From 9ada6e2acd15e78cd20025653b447188020e585f Mon Sep 17 00:00:00 2001 From: znetsixe <73483679+znetsixe@users.noreply.github.com> Date: Wed, 5 Nov 2025 15:47:05 +0100 Subject: [PATCH 01/10] Added support for maintenance tracking in hours. "getMaintenanceTimeHours" default in output of machine now --- src/configs/rotatingMachine.json | 36 +++++++++++++------------ src/state/state.js | 4 +++ src/state/stateConfig.json | 18 ++++++++++--- src/state/stateManager.js | 45 ++++++++++++++++++++++++++++---- 4 files changed, 79 insertions(+), 24 deletions(-) diff --git a/src/configs/rotatingMachine.json b/src/configs/rotatingMachine.json index 2c83be7..1baa685 100644 --- a/src/configs/rotatingMachine.json +++ b/src/configs/rotatingMachine.json @@ -245,10 +245,6 @@ { "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 of the machine." @@ -260,7 +256,7 @@ "type": "object", "schema":{ "auto": { - "default": ["statusCheck", "execMovement", "execSequence", "emergencyStop"], + "default": ["statusCheck", "execMovement", "execSequence", "emergencyStop","enterMaintenance"], "rules": { "type": "set", "itemType": "string", @@ -268,7 +264,7 @@ } }, "virtualControl": { - "default": ["statusCheck", "execMovement", "execSequence", "emergencyStop"], + "default": ["statusCheck", "execMovement", "execSequence", "emergencyStop","exitMaintenance"], "rules": { "type": "set", "itemType": "string", @@ -276,20 +272,13 @@ } }, "fysicalControl": { - "default": ["statusCheck", "emergencyStop"], + "default": ["statusCheck", "emergencyStop","enterMaintenance","exitMaintenance"], "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 by the machine." @@ -327,7 +316,6 @@ }, "description": "Information about valid command sources recognized by the machine." } - } }, "source": { "default": "parent", @@ -386,6 +374,22 @@ "itemType": "string", "description": "Sequence of states for booting up the machine." } + }, + "entermaintenance":{ + "default": ["stopping","coolingdown","idle","maintenance"], + "rules": { + "type": "set", + "itemType": "string", + "description": "Sequence of states if the machine is running to put it in maintenance state" + } + }, + "exitmaintenance":{ + "default": ["off","idle"], + "rules": { + "type": "set", + "itemType": "string", + "description": "Sequence of states if the machine is running to put it in maintenance state" + } } } }, diff --git a/src/state/state.js b/src/state/state.js index 2ad0229..abe7508 100644 --- a/src/state/state.js +++ b/src/state/state.js @@ -52,7 +52,11 @@ class state{ return this.stateManager.getRunTimeHours(); } + getMaintenanceTimeHours(){ + return this.stateManager.getMaintenanceTimeHours(); + } + async moveTo(targetPosition) { // Check for invalid conditions and throw errors diff --git a/src/state/stateConfig.json b/src/state/stateConfig.json index 99d398f..8a0b39b 100644 --- a/src/state/stateConfig.json +++ b/src/state/stateConfig.json @@ -205,6 +205,10 @@ { "value": "off", "description": "Machine is off." + }, + { + "value": "maintenance", + "description": "Machine locked for inspection or repair; automatic control disabled." } ], "description": "Current state of the machine." @@ -216,7 +220,7 @@ "type": "object", "schema": { "idle": { - "default": ["starting", "off","emergencystop"], + "default": ["starting", "off","emergencystop","maintenance"], "rules":{ "type": "set", "itemType": "string", @@ -280,7 +284,7 @@ } }, "off": { - "default": ["idle","emergencystop"], + "default": ["idle","emergencystop","maintenance"], "rules":{ "type": "set", "itemType": "string", @@ -288,12 +292,20 @@ } }, "emergencystop": { - "default": ["idle","off"], + "default": ["idle","off","maintenance"], "rules":{ "type": "set", "itemType": "string", "description": "Allowed transitions from emergency stop state." } + }, + "maintenance": { + "default": ["maintenance","idle","off"], + "rules":{ + "type": "set", + "itemType": "string", + "description": "Allowed transitions for maintenance mode" + } } }, "description": "Allowed transitions between states." diff --git a/src/state/stateManager.js b/src/state/stateManager.js index 4549308..9b3139f 100644 --- a/src/state/stateManager.js +++ b/src/state/stateManager.js @@ -48,10 +48,14 @@ class stateManager { // Define valid transitions (can be extended dynamically if needed) this.validTransitions = config.state.allowedTransitions; - // NEW: Initialize runtime tracking + //runtime tracking this.runTimeHours = 0; // cumulative runtime in hours this.runTimeStart = null; // timestamp when active state began + //maintenance tracking + this.maintenanceTimeStart = null; //timestamp when active state began + this.maintenanceTimeHours = 0; //cumulative + // Define active states (runtime counts only in these states) this.activeStates = config.state.activeStates; } @@ -59,7 +63,7 @@ class stateManager { getCurrentState() { return this.currentState; } - + transitionTo(newState,signal) { return new Promise((resolve, reject) => { if (signal && signal.aborted) { @@ -73,8 +77,9 @@ class stateManager { ); //go back early and reject promise } - // NEW: Handle runtime tracking based on active states + //Time tracking based on active states this.handleRuntimeTracking(newState); + this.handleMaintenancetimeTracking(newState); const transitionDuration = this.transitionTimes[this.currentState] || 0; // Default to 0 if no transition time this.logger.debug( @@ -100,7 +105,7 @@ class stateManager { } handleRuntimeTracking(newState) { - // NEW: Handle runtime tracking based on active states + //Handle runtime tracking based on active states const wasActive = this.activeStates.has(this.currentState); const willBeActive = this.activeStates.has(newState); if (wasActive && !willBeActive && this.runTimeStart) { @@ -120,6 +125,28 @@ class stateManager { } } + handleMaintenancetimeTracking(newState) { + //is this maintenance time ? + const wasActive = (this.currentState == "maintenance"? true:false); + const willBeActive = ( newState == "maintenance" ? true:false ); + + if (wasActive && this.maintenanceTimeStart) { + // stop runtime timer and accumulate elapsed time + const elapsed = (Date.now() - this.maintenanceTimeStart) / 3600000; // hours + this.maintenanceTimeHours += elapsed; + this.maintenanceTimeStart = null; + this.logger.debug( + `Maintenance timer stopped; elapsed=${elapsed.toFixed( + 3 + )}h, total=${this.maintenanceTimeHours.toFixed(3)}h.` + ); + } else if (willBeActive && !this.runTimeStart) { + // starting new runtime + this.maintenanceTimeStart = Date.now(); + this.logger.debug("Runtime timer started."); + } + } + isValidTransition(newState) { this.logger.debug( `Check 1 Transition valid ? From ${ @@ -150,7 +177,6 @@ class stateManager { return this.descriptions[state] || "No description available."; } - // NEW: Getter to retrieve current cumulative runtime (active time) in hours. getRunTimeHours() { // If currently active add the ongoing duration. let currentElapsed = 0; @@ -159,6 +185,15 @@ class stateManager { } return this.runTimeHours + currentElapsed; } + + getMaintenanceTimeHours() { + // If currently active add the ongoing duration. + let currentElapsed = 0; + if (this.maintenanceTimeStart) { + currentElapsed = (Date.now() - this.maintenanceTimeStart) / 3600000; + } + return this.maintenanceTimeHours + currentElapsed; + } } module.exports = stateManager; From 44adfdece641e0ec6ee95eea84fb951e2f3b308b Mon Sep 17 00:00:00 2001 From: znetsixe <73483679+znetsixe@users.noreply.github.com> Date: Wed, 5 Nov 2025 17:15:32 +0100 Subject: [PATCH 02/10] removed caps sensitivity --- src/configs/rotatingMachine.json | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/configs/rotatingMachine.json b/src/configs/rotatingMachine.json index 1baa685..744d9fb 100644 --- a/src/configs/rotatingMachine.json +++ b/src/configs/rotatingMachine.json @@ -256,7 +256,13 @@ "type": "object", "schema":{ "auto": { - "default": ["statusCheck", "execMovement", "execSequence", "emergencyStop","enterMaintenance"], + "default": [ + "statuscheck", + "execmovement", + "execsequence", + "emergencystop", + "entermaintenance" + ], "rules": { "type": "set", "itemType": "string", @@ -264,7 +270,13 @@ } }, "virtualControl": { - "default": ["statusCheck", "execMovement", "execSequence", "emergencyStop","exitMaintenance"], + "default": [ + "statuscheck", + "execmovement", + "execsequence", + "emergencystop", + "exitmaintenance" + ], "rules": { "type": "set", "itemType": "string", @@ -272,7 +284,12 @@ } }, "fysicalControl": { - "default": ["statusCheck", "emergencyStop","enterMaintenance","exitMaintenance"], + "default": [ + "statuscheck", + "emergencystop", + "entermaintenance", + "exitmaintenance" + ], "rules": { "type": "set", "itemType": "string", @@ -282,8 +299,7 @@ } }, "description": "Information about valid command sources recognized by the machine." - } - }, + }, "allowedSources":{ "default": {}, "rules": { @@ -316,6 +332,7 @@ }, "description": "Information about valid command sources recognized by the machine." } + } }, "source": { "default": "parent", From 98cd44d3ae2106a459adee1bfebb8c13e37a695a Mon Sep 17 00:00:00 2001 From: znetsixe <73483679+znetsixe@users.noreply.github.com> Date: Thu, 6 Nov 2025 11:18:54 +0100 Subject: [PATCH 03/10] updated output utils bug fixes for formatting --- src/helper/outputUtils.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/helper/outputUtils.js b/src/helper/outputUtils.js index bd6fb8d..744b2ea 100644 --- a/src/helper/outputUtils.js +++ b/src/helper/outputUtils.js @@ -64,7 +64,7 @@ class OutputUtils { influxDBFormat(changedFields, config , flatTags) { // Create the measurement and topic using softwareType and name config.functionality.softwareType + . - const measurement = config.general.name; + const measurement = `${config.functionality?.softwareType}_${config.general?.id}`; const payload = { measurement: measurement, fields: changedFields, @@ -104,24 +104,23 @@ class OutputUtils { return { // general properties id: config.general?.id, - name: config.general?.name, - unit: config.general?.unit, // functionality properties softwareType: config.functionality?.softwareType, role: config.functionality?.role, // asset properties (exclude machineCurve) uuid: config.asset?.uuid, + tagcode: config.asset?.tagcode, geoLocation: config.asset?.geoLocation, - supplier: config.asset?.supplier, + category: config.asset?.category, type: config.asset?.type, - subType: config.asset?.subType, model: config.asset?.model, + unit: config.general?.unit, }; } processFormat(changedFields,config) { // Create the measurement and topic using softwareType and name config.functionality.softwareType + . - const measurement = config.general.name; + const measurement = `${config.functionality?.softwareType}_${config.general?.id}`; const payload = changedFields; const topic = measurement; const msg = { topic: topic, payload: payload }; From 4a489acd893508b22e931ffd7833f74c61609409 Mon Sep 17 00:00:00 2001 From: znetsixe <73483679+znetsixe@users.noreply.github.com> Date: Thu, 6 Nov 2025 16:47:17 +0100 Subject: [PATCH 04/10] some formatting --- datasets/assetData/assetData.json | 1 + datasets/tagcodeapp_assets.json | 1363 ++++++++++++++++++++++++++++- src/configs/index.js | 40 +- 3 files changed, 1383 insertions(+), 21 deletions(-) diff --git a/datasets/assetData/assetData.json b/datasets/assetData/assetData.json index 03eb639..3c36bd9 100644 --- a/datasets/assetData/assetData.json +++ b/datasets/assetData/assetData.json @@ -83,6 +83,7 @@ { "id": "hidrostal-pump-001", "name": "hidrostal-H05K-S03R", + "units": ["l/s"] }, { diff --git a/datasets/tagcodeapp_assets.json b/datasets/tagcodeapp_assets.json index ec2d0b9..fb45e9d 100644 --- a/datasets/tagcodeapp_assets.json +++ b/datasets/tagcodeapp_assets.json @@ -1 +1,1362 @@ -{"success":true,"data":[{"asset_id":"1","profile_id":"0","asset_tag_number":"L001","asset_name":"Test laptop R&D","asset_description":"Test voor een laptop voor R&D","asset_date_created":"2025-01-16 14:19:32","asset_status":"actief ","product_model_id":"1","location_id":"1","product_model_uuid":"0123456789","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Macbook Air 12","product_model_description":null,"product_subtype_name":"Laptop","profile_name":null,"process_name":"Voorbezinking"},{"asset_id":"2","profile_id":"1","asset_tag_number":"L9764","asset_name":"AssetNaam","asset_description":"Beschrijving","asset_date_created":"2025-01-18 19:03:15","asset_status":"actief","product_model_id":"1","location_id":"1","product_model_uuid":"123456789","process_id":"11","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Macbook Air 12","product_model_description":null,"product_subtype_name":"Laptop","profile_name":"Watersystemen","process_name":null},{"asset_id":"3","profile_id":"1","asset_tag_number":"L6616","asset_name":"AssetNaam","asset_description":"Beschrijving","asset_date_created":"2025-01-18 19:03:47","asset_status":"actief","product_model_id":null,"location_id":null,"product_model_uuid":null,"process_id":null,"location_latitude":null,"location_longitude":null,"location_name":null,"location_description":null,"location_address":null,"product_model_name":null,"product_model_description":null,"product_subtype_name":null,"profile_name":"Watersystemen","process_name":null},{"asset_id":"4","profile_id":"1","asset_tag_number":"L4972","asset_name":"AssetNaam","asset_description":"Beschrijving","asset_date_created":"2025-01-18 19:05:18","asset_status":"actief","product_model_id":"1","location_id":"1","product_model_uuid":"123456789","process_id":"0","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Macbook Air 12","product_model_description":null,"product_subtype_name":"Laptop","profile_name":"Watersystemen","process_name":null},{"asset_id":"5","profile_id":"1","asset_tag_number":"L3586","asset_name":"AssetNaam","asset_description":"Beschrijving","asset_date_created":"2025-01-18 19:05:48","asset_status":"actief","product_model_id":"1","location_id":"1","product_model_uuid":"1234567890","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Macbook Air 12","product_model_description":null,"product_subtype_name":"Laptop","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"6","profile_id":"1","asset_tag_number":"L6719","asset_name":"AssetNaam","asset_description":"Beschrijving","asset_date_created":"2025-01-18 19:06:38","asset_status":"inactief","product_model_id":"2","location_id":"1","product_model_uuid":"12357890","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Macbook Air 13","product_model_description":null,"product_subtype_name":"Laptop","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"7","profile_id":"1","asset_tag_number":"P6565","asset_name":"sensor","asset_description":"Computer","asset_date_created":"2025-02-03 08:50:24","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"52","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":null},{"asset_id":"8","profile_id":"1","asset_tag_number":"P9573","asset_name":"sensor","asset_description":"Test3","asset_date_created":"2025-02-03 08:56:57","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"9","profile_id":"1","asset_tag_number":"P8081","asset_name":"sensor","asset_description":"Test4","asset_date_created":"2025-02-03 08:58:52","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"10","profile_id":"1","asset_tag_number":"P3179","asset_name":"sensor","asset_description":"Pressure Transmitter 1","asset_date_created":"2025-02-03 09:08:37","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"11","profile_id":"1","asset_tag_number":"P7975","asset_name":"sensor","asset_description":"PT99","asset_date_created":"2025-02-03 10:12:48","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"12","profile_id":"1","asset_tag_number":"P7871","asset_name":"sensor","asset_description":"PT99","asset_date_created":"2025-02-03 10:50:39","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"13","profile_id":"1","asset_tag_number":"P9288","asset_name":"sensor","asset_description":"PT99","asset_date_created":"2025-02-03 10:50:43","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"14","profile_id":"1","asset_tag_number":"19468","asset_name":"AssetNaam","asset_description":"Beschrijving","asset_date_created":"2025-02-13 14:12:12","asset_status":"actief","product_model_id":"1","location_id":"1","product_model_uuid":"123456789","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Macbook Air 12","product_model_description":null,"product_subtype_name":"Laptop","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"15","profile_id":"1","asset_tag_number":"18804","asset_name":"AssetNaam","asset_description":"Beschrijving","asset_date_created":"2025-02-13 14:13:54","asset_status":"actief","product_model_id":"1","location_id":"1","product_model_uuid":"123456789","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Macbook Air 12","product_model_description":null,"product_subtype_name":"Laptop","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"16","profile_id":"1","asset_tag_number":"11795","asset_name":"AssetNaam","asset_description":"Beschrijving","asset_date_created":"2025-02-13 14:13:55","asset_status":"actief","product_model_id":"1","location_id":"1","product_model_uuid":"123456789","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Macbook Air 12","product_model_description":null,"product_subtype_name":"Laptop","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"17","profile_id":"1","asset_tag_number":"16230","asset_name":"AssetNaam","asset_description":"Beschrijving","asset_date_created":"2025-02-13 14:14:37","asset_status":"actief","product_model_id":"1","location_id":"1","product_model_uuid":"123456789","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Macbook Air 12","product_model_description":null,"product_subtype_name":"Laptop","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"18","profile_id":"1","asset_tag_number":"14600","asset_name":"AssetNaam","asset_description":"Beschrijving","asset_date_created":"2025-02-13 14:14:38","asset_status":"actief","product_model_id":"1","location_id":"1","product_model_uuid":"123456789","process_id":"11","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Macbook Air 12","product_model_description":null,"product_subtype_name":"Laptop","profile_name":"Watersystemen","process_name":null},{"asset_id":"19","profile_id":"1","asset_tag_number":"16535","asset_name":"AssetNaam2","asset_description":"Beschrijving","asset_date_created":"2025-02-13 14:14:52","asset_status":"actief","product_model_id":"1","location_id":"1","product_model_uuid":"123453789","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Macbook Air 12","product_model_description":null,"product_subtype_name":"Laptop","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"20","profile_id":"1","asset_tag_number":"61894","asset_name":"sensor","asset_description":"PT1","asset_date_created":"2025-02-13 15:26:47","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"21","profile_id":"1","asset_tag_number":"62803","asset_name":"sensor","asset_description":"PT999","asset_date_created":"2025-02-13 15:27:06","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"22","profile_id":"1","asset_tag_number":"63753","asset_name":"sensor","asset_description":"PT999","asset_date_created":"2025-02-13 15:27:32","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"23","profile_id":"1","asset_tag_number":"undefined","asset_name":"AssetNaam","asset_description":"Beschrijving","asset_date_created":"2025-02-13 15:46:59","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"24","profile_id":"1","asset_tag_number":"null","asset_name":"AssetNaam","asset_description":"Beschrijving","asset_date_created":"2025-02-13 15:49:03","asset_status":"actief","product_model_id":"1","location_id":"1","product_model_uuid":"123456789","process_id":"11","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Macbook Air 12","product_model_description":null,"product_subtype_name":"Laptop","profile_name":"Watersystemen","process_name":null},{"asset_id":"26","profile_id":"1","asset_tag_number":"19134","asset_name":"AssetNaam","asset_description":"Beschrijving","asset_date_created":"2025-02-13 15:50:36","asset_status":"actief","product_model_id":"1","location_id":"1","product_model_uuid":"123456789","process_id":"11","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Macbook Air 12","product_model_description":null,"product_subtype_name":"Laptop","profile_name":"Watersystemen","process_name":null},{"asset_id":"27","profile_id":"1","asset_tag_number":"63247","asset_name":"sensor","asset_description":"PT1","asset_date_created":"2025-02-13 15:52:43","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"28","profile_id":"1","asset_tag_number":"62149","asset_name":"sensor","asset_description":"PT2","asset_date_created":"2025-02-13 15:52:53","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"29","profile_id":"1","asset_tag_number":"61688","asset_name":"sensor","asset_description":"PT1","asset_date_created":"2025-02-13 15:56:50","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"30","profile_id":"1","asset_tag_number":"62927","asset_name":"sensor","asset_description":"PT1","asset_date_created":"2025-02-13 16:02:12","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"31","profile_id":"1","asset_tag_number":"66465","asset_name":"sensor","asset_description":"PT1","asset_date_created":"2025-02-13 16:02:29","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"32","profile_id":"1","asset_tag_number":"65180","asset_name":"sensor","asset_description":"PT1","asset_date_created":"2025-02-13 16:05:07","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":"Voorbezinking"},{"asset_id":"33","profile_id":"1","asset_tag_number":"65992","asset_name":"AssetNaamtest","asset_description":"Beschrijving","asset_date_created":"2025-02-13 16:05:18","asset_status":"actief","product_model_id":"1","location_id":"1","product_model_uuid":"123456789","process_id":"11","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Macbook Air 12","product_model_description":null,"product_subtype_name":"Laptop","profile_name":"Watersystemen","process_name":null},{"asset_id":"34","profile_id":"1","asset_tag_number":"67315","asset_name":"AssetNaamtest","asset_description":"Beschrijving","asset_date_created":"2025-02-13 16:09:42","asset_status":"actief","product_model_id":"1","location_id":"1","product_model_uuid":"123456789","process_id":"11","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Macbook Air 12","product_model_description":null,"product_subtype_name":"Laptop","profile_name":"Watersystemen","process_name":null},{"asset_id":"35","profile_id":"1","asset_tag_number":"18140","asset_name":"AssetNaamtest","asset_description":"Beschrijving","asset_date_created":"2025-02-13 16:22:37","asset_status":"actief","product_model_id":"1","location_id":"1","product_model_uuid":"123456789","process_id":"11","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Macbook Air 12","product_model_description":null,"product_subtype_name":"Laptop","profile_name":"Watersystemen","process_name":null},{"asset_id":"36","profile_id":"1","asset_tag_number":"L000035","asset_name":"AssetNaamtest","asset_description":"Beschrijving","asset_date_created":"2025-02-13 16:22:57","asset_status":"actief","product_model_id":"1","location_id":"1","product_model_uuid":"123456789","process_id":"11","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Macbook Air 12","product_model_description":null,"product_subtype_name":"Laptop","profile_name":"Watersystemen","process_name":null},{"asset_id":"37","profile_id":"1","asset_tag_number":"L000036","asset_name":"AssetNaamtest","asset_description":"Beschrijving","asset_date_created":"2025-02-13 16:31:26","asset_status":"actief","product_model_id":"1","location_id":"1","product_model_uuid":"123456789","process_id":"11","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Macbook Air 12","product_model_description":null,"product_subtype_name":"Laptop","profile_name":"Watersystemen","process_name":null},{"asset_id":"38","profile_id":"1","asset_tag_number":"P000037","asset_name":"sensor","asset_description":"testasdf","asset_date_created":"2025-02-13 16:47:33","asset_status":"actief","product_model_id":null,"location_id":null,"product_model_uuid":null,"process_id":null,"location_latitude":null,"location_longitude":null,"location_name":null,"location_description":null,"location_address":null,"product_model_name":null,"product_model_description":null,"product_subtype_name":null,"profile_name":"Watersystemen","process_name":null},{"asset_id":"39","profile_id":"1","asset_tag_number":"P000038","asset_name":"pump","asset_description":"m1","asset_date_created":"2025-02-20 13:12:36","asset_status":"actief","product_model_id":null,"location_id":null,"product_model_uuid":null,"process_id":null,"location_latitude":null,"location_longitude":null,"location_name":null,"location_description":null,"location_address":null,"product_model_name":null,"product_model_description":null,"product_subtype_name":null,"profile_name":"Watersystemen","process_name":null},{"asset_id":"40","profile_id":"1","asset_tag_number":"P000039","asset_name":"pump","asset_description":"m1","asset_date_created":"2025-02-20 13:13:17","asset_status":"actief","product_model_id":null,"location_id":null,"product_model_uuid":null,"process_id":null,"location_latitude":null,"location_longitude":null,"location_name":null,"location_description":null,"location_address":null,"product_model_name":null,"product_model_description":null,"product_subtype_name":null,"profile_name":"Watersystemen","process_name":null},{"asset_id":"41","profile_id":"1","asset_tag_number":"P000040","asset_name":"pump","asset_description":"m1","asset_date_created":"2025-02-20 13:15:26","asset_status":"actief","product_model_id":null,"location_id":null,"product_model_uuid":null,"process_id":null,"location_latitude":null,"location_longitude":null,"location_name":null,"location_description":null,"location_address":null,"product_model_name":null,"product_model_description":null,"product_subtype_name":null,"profile_name":"Watersystemen","process_name":null},{"asset_id":"42","profile_id":"1","asset_tag_number":"P000041","asset_name":"pump","asset_description":"m1","asset_date_created":"2025-02-20 13:22:02","asset_status":"actief","product_model_id":null,"location_id":null,"product_model_uuid":null,"process_id":null,"location_latitude":null,"location_longitude":null,"location_name":null,"location_description":null,"location_address":null,"product_model_name":null,"product_model_description":null,"product_subtype_name":null,"profile_name":"Watersystemen","process_name":null},{"asset_id":"43","profile_id":"1","asset_tag_number":"D000042","asset_name":"test","asset_description":"test","asset_date_created":"2025-02-20 13:35:30","asset_status":"inactief","product_model_id":null,"location_id":null,"product_model_uuid":null,"process_id":null,"location_latitude":null,"location_longitude":null,"location_name":null,"location_description":null,"location_address":null,"product_model_name":null,"product_model_description":null,"product_subtype_name":null,"profile_name":"Watersystemen","process_name":null},{"asset_id":"44","profile_id":"1","asset_tag_number":"D000043","asset_name":"gdxg","asset_description":"dssdsdsd","asset_date_created":"2025-02-20 13:36:50","asset_status":"inactief","product_model_id":null,"location_id":null,"product_model_uuid":null,"process_id":null,"location_latitude":null,"location_longitude":null,"location_name":null,"location_description":null,"location_address":null,"product_model_name":null,"product_model_description":null,"product_subtype_name":null,"profile_name":"Watersystemen","process_name":null},{"asset_id":"45","profile_id":"1","asset_tag_number":"P000044","asset_name":"pump","asset_description":"m1","asset_date_created":"2025-02-20 14:06:53","asset_status":"actief","product_model_id":null,"location_id":null,"product_model_uuid":null,"process_id":null,"location_latitude":null,"location_longitude":null,"location_name":null,"location_description":null,"location_address":null,"product_model_name":null,"product_model_description":null,"product_subtype_name":null,"profile_name":"Watersystemen","process_name":null},{"asset_id":"46","profile_id":"1","asset_tag_number":"P000045","asset_name":"pump","asset_description":"m1","asset_date_created":"2025-02-20 15:06:34","asset_status":"actief","product_model_id":null,"location_id":null,"product_model_uuid":null,"process_id":null,"location_latitude":null,"location_longitude":null,"location_name":null,"location_description":null,"location_address":null,"product_model_name":null,"product_model_description":null,"product_subtype_name":null,"profile_name":"Watersystemen","process_name":null},{"asset_id":"47","profile_id":"1","asset_tag_number":"P000046","asset_name":"pump","asset_description":"m1","asset_date_created":"2025-02-20 15:23:00","asset_status":"actief","product_model_id":null,"location_id":null,"product_model_uuid":null,"process_id":null,"location_latitude":null,"location_longitude":null,"location_name":null,"location_description":null,"location_address":null,"product_model_name":null,"product_model_description":null,"product_subtype_name":null,"profile_name":"Watersystemen","process_name":null},{"asset_id":"48","profile_id":"1","asset_tag_number":"P000047","asset_name":"pump","asset_description":"m1","asset_date_created":"2025-02-20 15:23:05","asset_status":"actief","product_model_id":null,"location_id":null,"product_model_uuid":null,"process_id":null,"location_latitude":null,"location_longitude":null,"location_name":null,"location_description":null,"location_address":null,"product_model_name":null,"product_model_description":null,"product_subtype_name":null,"profile_name":"Watersystemen","process_name":null},{"asset_id":"49","profile_id":"1","asset_tag_number":"P000048","asset_name":"pump","asset_description":"m2","asset_date_created":"2025-02-20 15:24:47","asset_status":"actief","product_model_id":null,"location_id":null,"product_model_uuid":null,"process_id":null,"location_latitude":null,"location_longitude":null,"location_name":null,"location_description":null,"location_address":null,"product_model_name":null,"product_model_description":null,"product_subtype_name":null,"profile_name":"Watersystemen","process_name":null},{"asset_id":"50","profile_id":"1","asset_tag_number":"P000049","asset_name":"pump","asset_description":"m1","asset_date_created":"2025-02-20 15:24:55","asset_status":"actief","product_model_id":null,"location_id":null,"product_model_uuid":null,"process_id":null,"location_latitude":null,"location_longitude":null,"location_name":null,"location_description":null,"location_address":null,"product_model_name":null,"product_model_description":null,"product_subtype_name":null,"profile_name":"Watersystemen","process_name":null},{"asset_id":"51","profile_id":"1","asset_tag_number":"P000050","asset_name":"pump","asset_description":"m1","asset_date_created":"2025-02-20 15:27:36","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"2147483647","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":null},{"asset_id":"52","profile_id":"1","asset_tag_number":"P000051","asset_name":"pump","asset_description":"m1","asset_date_created":"2025-02-20 15:29:41","asset_status":"actief","product_model_id":null,"location_id":null,"product_model_uuid":null,"process_id":null,"location_latitude":null,"location_longitude":null,"location_name":null,"location_description":null,"location_address":null,"product_model_name":null,"product_model_description":null,"product_subtype_name":null,"profile_name":"Watersystemen","process_name":null},{"asset_id":"53","profile_id":"1","asset_tag_number":"P000052","asset_name":"pump","asset_description":"m1","asset_date_created":"2025-02-20 15:30:53","asset_status":"actief","product_model_id":null,"location_id":null,"product_model_uuid":null,"process_id":null,"location_latitude":null,"location_longitude":null,"location_name":null,"location_description":null,"location_address":null,"product_model_name":null,"product_model_description":null,"product_subtype_name":null,"profile_name":"Watersystemen","process_name":null},{"asset_id":"54","profile_id":"1","asset_tag_number":"P000053","asset_name":"pump","asset_description":"m1","asset_date_created":"2025-02-20 15:31:48","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"2147483647","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":null},{"asset_id":"55","profile_id":"1","asset_tag_number":"P000054","asset_name":"pump","asset_description":"m1","asset_date_created":"2025-02-20 15:36:19","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"2147483647","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":null},{"asset_id":"56","profile_id":"1","asset_tag_number":"P000055","asset_name":"pump","asset_description":"m1","asset_date_created":"2025-02-20 15:42:37","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"2147483647","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":null},{"asset_id":"57","profile_id":"1","asset_tag_number":"P000056","asset_name":"pump","asset_description":"m2","asset_date_created":"2025-02-20 15:43:46","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"2147483647","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":null},{"asset_id":"58","profile_id":"1","asset_tag_number":"P000057","asset_name":"pump","asset_description":"m1","asset_date_created":"2025-02-20 15:46:10","asset_status":"actief","product_model_id":null,"location_id":null,"product_model_uuid":null,"process_id":null,"location_latitude":null,"location_longitude":null,"location_name":null,"location_description":null,"location_address":null,"product_model_name":null,"product_model_description":null,"product_subtype_name":null,"profile_name":"Watersystemen","process_name":null},{"asset_id":"59","profile_id":"1","asset_tag_number":"P000058","asset_name":"pump","asset_description":"m1","asset_date_created":"2025-02-20 15:46:38","asset_status":"actief","product_model_id":"6","location_id":"1","product_model_uuid":"123456789","process_id":"2147483647","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Vegabar 14","product_model_description":"vegabar 14","product_subtype_name":"pressure","profile_name":"Watersystemen","process_name":null},{"asset_id":"60","profile_id":"1","asset_tag_number":"P000059","asset_name":"sensor","asset_description":"PT110","asset_date_created":"2025-02-25 15:25:01","asset_status":"actief","product_model_id":"1","location_id":"1","product_model_uuid":"b02b3a50-3f36-4ab0-9479-84de2fca9cb1","process_id":"1","location_latitude":"51.6314","location_longitude":"4.708","location_name":"RWZI Nieuwveer","location_description":null,"location_address":null,"product_model_name":"Macbook Air 12","product_model_description":null,"product_subtype_name":"Laptop","profile_name":"Watersystemen","process_name":"Voorbezinking"}]} \ No newline at end of file +{ + "success": true, + "data": [ + { + "asset_id": "1", + "profile_id": "0", + "asset_tag_number": "L001", + "asset_name": "Test laptop R&D", + "asset_description": "Test voor een laptop voor R&D", + "asset_date_created": "2025-01-16 14:19:32", + "asset_status": "actief ", + "product_model_id": "1", + "location_id": "1", + "product_model_uuid": "0123456789", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Macbook Air 12", + "product_model_description": null, + "product_subtype_name": "Laptop", + "profile_name": null, + "process_name": "Voorbezinking" + }, + { + "asset_id": "2", + "profile_id": "1", + "asset_tag_number": "L9764", + "asset_name": "AssetNaam", + "asset_description": "Beschrijving", + "asset_date_created": "2025-01-18 19:03:15", + "asset_status": "actief", + "product_model_id": "1", + "location_id": "1", + "product_model_uuid": "123456789", + "process_id": "11", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Macbook Air 12", + "product_model_description": null, + "product_subtype_name": "Laptop", + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "3", + "profile_id": "1", + "asset_tag_number": "L6616", + "asset_name": "AssetNaam", + "asset_description": "Beschrijving", + "asset_date_created": "2025-01-18 19:03:47", + "asset_status": "actief", + "product_model_id": null, + "location_id": null, + "product_model_uuid": null, + "process_id": null, + "location_latitude": null, + "location_longitude": null, + "location_name": null, + "location_description": null, + "location_address": null, + "product_model_name": null, + "product_model_description": null, + "product_subtype_name": null, + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "4", + "profile_id": "1", + "asset_tag_number": "L4972", + "asset_name": "AssetNaam", + "asset_description": "Beschrijving", + "asset_date_created": "2025-01-18 19:05:18", + "asset_status": "actief", + "product_model_id": "1", + "location_id": "1", + "product_model_uuid": "123456789", + "process_id": "0", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Macbook Air 12", + "product_model_description": null, + "product_subtype_name": "Laptop", + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "5", + "profile_id": "1", + "asset_tag_number": "L3586", + "asset_name": "AssetNaam", + "asset_description": "Beschrijving", + "asset_date_created": "2025-01-18 19:05:48", + "asset_status": "actief", + "product_model_id": "1", + "location_id": "1", + "product_model_uuid": "1234567890", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Macbook Air 12", + "product_model_description": null, + "product_subtype_name": "Laptop", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "6", + "profile_id": "1", + "asset_tag_number": "L6719", + "asset_name": "AssetNaam", + "asset_description": "Beschrijving", + "asset_date_created": "2025-01-18 19:06:38", + "asset_status": "inactief", + "product_model_id": "2", + "location_id": "1", + "product_model_uuid": "12357890", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Macbook Air 13", + "product_model_description": null, + "product_subtype_name": "Laptop", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "7", + "profile_id": "1", + "asset_tag_number": "P6565", + "asset_name": "sensor", + "asset_description": "Computer", + "asset_date_created": "2025-02-03 08:50:24", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "52", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "8", + "profile_id": "1", + "asset_tag_number": "P9573", + "asset_name": "sensor", + "asset_description": "Test3", + "asset_date_created": "2025-02-03 08:56:57", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "9", + "profile_id": "1", + "asset_tag_number": "P8081", + "asset_name": "sensor", + "asset_description": "Test4", + "asset_date_created": "2025-02-03 08:58:52", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "10", + "profile_id": "1", + "asset_tag_number": "P3179", + "asset_name": "sensor", + "asset_description": "Pressure Transmitter 1", + "asset_date_created": "2025-02-03 09:08:37", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "11", + "profile_id": "1", + "asset_tag_number": "P7975", + "asset_name": "sensor", + "asset_description": "PT99", + "asset_date_created": "2025-02-03 10:12:48", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "12", + "profile_id": "1", + "asset_tag_number": "P7871", + "asset_name": "sensor", + "asset_description": "PT99", + "asset_date_created": "2025-02-03 10:50:39", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "13", + "profile_id": "1", + "asset_tag_number": "P9288", + "asset_name": "sensor", + "asset_description": "PT99", + "asset_date_created": "2025-02-03 10:50:43", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "14", + "profile_id": "1", + "asset_tag_number": "19468", + "asset_name": "AssetNaam", + "asset_description": "Beschrijving", + "asset_date_created": "2025-02-13 14:12:12", + "asset_status": "actief", + "product_model_id": "1", + "location_id": "1", + "product_model_uuid": "123456789", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Macbook Air 12", + "product_model_description": null, + "product_subtype_name": "Laptop", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "15", + "profile_id": "1", + "asset_tag_number": "18804", + "asset_name": "AssetNaam", + "asset_description": "Beschrijving", + "asset_date_created": "2025-02-13 14:13:54", + "asset_status": "actief", + "product_model_id": "1", + "location_id": "1", + "product_model_uuid": "123456789", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Macbook Air 12", + "product_model_description": null, + "product_subtype_name": "Laptop", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "16", + "profile_id": "1", + "asset_tag_number": "11795", + "asset_name": "AssetNaam", + "asset_description": "Beschrijving", + "asset_date_created": "2025-02-13 14:13:55", + "asset_status": "actief", + "product_model_id": "1", + "location_id": "1", + "product_model_uuid": "123456789", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Macbook Air 12", + "product_model_description": null, + "product_subtype_name": "Laptop", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "17", + "profile_id": "1", + "asset_tag_number": "16230", + "asset_name": "AssetNaam", + "asset_description": "Beschrijving", + "asset_date_created": "2025-02-13 14:14:37", + "asset_status": "actief", + "product_model_id": "1", + "location_id": "1", + "product_model_uuid": "123456789", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Macbook Air 12", + "product_model_description": null, + "product_subtype_name": "Laptop", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "18", + "profile_id": "1", + "asset_tag_number": "14600", + "asset_name": "AssetNaam", + "asset_description": "Beschrijving", + "asset_date_created": "2025-02-13 14:14:38", + "asset_status": "actief", + "product_model_id": "1", + "location_id": "1", + "product_model_uuid": "123456789", + "process_id": "11", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Macbook Air 12", + "product_model_description": null, + "product_subtype_name": "Laptop", + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "19", + "profile_id": "1", + "asset_tag_number": "16535", + "asset_name": "AssetNaam2", + "asset_description": "Beschrijving", + "asset_date_created": "2025-02-13 14:14:52", + "asset_status": "actief", + "product_model_id": "1", + "location_id": "1", + "product_model_uuid": "123453789", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Macbook Air 12", + "product_model_description": null, + "product_subtype_name": "Laptop", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "20", + "profile_id": "1", + "asset_tag_number": "61894", + "asset_name": "sensor", + "asset_description": "PT1", + "asset_date_created": "2025-02-13 15:26:47", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "21", + "profile_id": "1", + "asset_tag_number": "62803", + "asset_name": "sensor", + "asset_description": "PT999", + "asset_date_created": "2025-02-13 15:27:06", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "22", + "profile_id": "1", + "asset_tag_number": "63753", + "asset_name": "sensor", + "asset_description": "PT999", + "asset_date_created": "2025-02-13 15:27:32", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "23", + "profile_id": "1", + "asset_tag_number": "undefined", + "asset_name": "AssetNaam", + "asset_description": "Beschrijving", + "asset_date_created": "2025-02-13 15:46:59", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "24", + "profile_id": "1", + "asset_tag_number": "null", + "asset_name": "AssetNaam", + "asset_description": "Beschrijving", + "asset_date_created": "2025-02-13 15:49:03", + "asset_status": "actief", + "product_model_id": "1", + "location_id": "1", + "product_model_uuid": "123456789", + "process_id": "11", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Macbook Air 12", + "product_model_description": null, + "product_subtype_name": "Laptop", + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "26", + "profile_id": "1", + "asset_tag_number": "19134", + "asset_name": "AssetNaam", + "asset_description": "Beschrijving", + "asset_date_created": "2025-02-13 15:50:36", + "asset_status": "actief", + "product_model_id": "1", + "location_id": "1", + "product_model_uuid": "123456789", + "process_id": "11", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Macbook Air 12", + "product_model_description": null, + "product_subtype_name": "Laptop", + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "27", + "profile_id": "1", + "asset_tag_number": "63247", + "asset_name": "sensor", + "asset_description": "PT1", + "asset_date_created": "2025-02-13 15:52:43", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "28", + "profile_id": "1", + "asset_tag_number": "62149", + "asset_name": "sensor", + "asset_description": "PT2", + "asset_date_created": "2025-02-13 15:52:53", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "29", + "profile_id": "1", + "asset_tag_number": "61688", + "asset_name": "sensor", + "asset_description": "PT1", + "asset_date_created": "2025-02-13 15:56:50", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "30", + "profile_id": "1", + "asset_tag_number": "62927", + "asset_name": "sensor", + "asset_description": "PT1", + "asset_date_created": "2025-02-13 16:02:12", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "31", + "profile_id": "1", + "asset_tag_number": "66465", + "asset_name": "sensor", + "asset_description": "PT1", + "asset_date_created": "2025-02-13 16:02:29", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "32", + "profile_id": "1", + "asset_tag_number": "65180", + "asset_name": "sensor", + "asset_description": "PT1", + "asset_date_created": "2025-02-13 16:05:07", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + }, + { + "asset_id": "33", + "profile_id": "1", + "asset_tag_number": "65992", + "asset_name": "AssetNaamtest", + "asset_description": "Beschrijving", + "asset_date_created": "2025-02-13 16:05:18", + "asset_status": "actief", + "product_model_id": "1", + "location_id": "1", + "product_model_uuid": "123456789", + "process_id": "11", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Macbook Air 12", + "product_model_description": null, + "product_subtype_name": "Laptop", + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "34", + "profile_id": "1", + "asset_tag_number": "67315", + "asset_name": "AssetNaamtest", + "asset_description": "Beschrijving", + "asset_date_created": "2025-02-13 16:09:42", + "asset_status": "actief", + "product_model_id": "1", + "location_id": "1", + "product_model_uuid": "123456789", + "process_id": "11", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Macbook Air 12", + "product_model_description": null, + "product_subtype_name": "Laptop", + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "35", + "profile_id": "1", + "asset_tag_number": "18140", + "asset_name": "AssetNaamtest", + "asset_description": "Beschrijving", + "asset_date_created": "2025-02-13 16:22:37", + "asset_status": "actief", + "product_model_id": "1", + "location_id": "1", + "product_model_uuid": "123456789", + "process_id": "11", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Macbook Air 12", + "product_model_description": null, + "product_subtype_name": "Laptop", + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "36", + "profile_id": "1", + "asset_tag_number": "L000035", + "asset_name": "AssetNaamtest", + "asset_description": "Beschrijving", + "asset_date_created": "2025-02-13 16:22:57", + "asset_status": "actief", + "product_model_id": "1", + "location_id": "1", + "product_model_uuid": "123456789", + "process_id": "11", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Macbook Air 12", + "product_model_description": null, + "product_subtype_name": "Laptop", + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "37", + "profile_id": "1", + "asset_tag_number": "L000036", + "asset_name": "AssetNaamtest", + "asset_description": "Beschrijving", + "asset_date_created": "2025-02-13 16:31:26", + "asset_status": "actief", + "product_model_id": "1", + "location_id": "1", + "product_model_uuid": "123456789", + "process_id": "11", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Macbook Air 12", + "product_model_description": null, + "product_subtype_name": "Laptop", + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "38", + "profile_id": "1", + "asset_tag_number": "P000037", + "asset_name": "sensor", + "asset_description": "testasdf", + "asset_date_created": "2025-02-13 16:47:33", + "asset_status": "actief", + "product_model_id": null, + "location_id": null, + "product_model_uuid": null, + "process_id": null, + "location_latitude": null, + "location_longitude": null, + "location_name": null, + "location_description": null, + "location_address": null, + "product_model_name": null, + "product_model_description": null, + "product_subtype_name": null, + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "39", + "profile_id": "1", + "asset_tag_number": "P000038", + "asset_name": "pump", + "asset_description": "m1", + "asset_date_created": "2025-02-20 13:12:36", + "asset_status": "actief", + "product_model_id": null, + "location_id": null, + "product_model_uuid": null, + "process_id": null, + "location_latitude": null, + "location_longitude": null, + "location_name": null, + "location_description": null, + "location_address": null, + "product_model_name": null, + "product_model_description": null, + "product_subtype_name": null, + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "40", + "profile_id": "1", + "asset_tag_number": "P000039", + "asset_name": "pump", + "asset_description": "m1", + "asset_date_created": "2025-02-20 13:13:17", + "asset_status": "actief", + "product_model_id": null, + "location_id": null, + "product_model_uuid": null, + "process_id": null, + "location_latitude": null, + "location_longitude": null, + "location_name": null, + "location_description": null, + "location_address": null, + "product_model_name": null, + "product_model_description": null, + "product_subtype_name": null, + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "41", + "profile_id": "1", + "asset_tag_number": "P000040", + "asset_name": "pump", + "asset_description": "m1", + "asset_date_created": "2025-02-20 13:15:26", + "asset_status": "actief", + "product_model_id": null, + "location_id": null, + "product_model_uuid": null, + "process_id": null, + "location_latitude": null, + "location_longitude": null, + "location_name": null, + "location_description": null, + "location_address": null, + "product_model_name": null, + "product_model_description": null, + "product_subtype_name": null, + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "42", + "profile_id": "1", + "asset_tag_number": "P000041", + "asset_name": "pump", + "asset_description": "m1", + "asset_date_created": "2025-02-20 13:22:02", + "asset_status": "actief", + "product_model_id": null, + "location_id": null, + "product_model_uuid": null, + "process_id": null, + "location_latitude": null, + "location_longitude": null, + "location_name": null, + "location_description": null, + "location_address": null, + "product_model_name": null, + "product_model_description": null, + "product_subtype_name": null, + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "43", + "profile_id": "1", + "asset_tag_number": "D000042", + "asset_name": "test", + "asset_description": "test", + "asset_date_created": "2025-02-20 13:35:30", + "asset_status": "inactief", + "product_model_id": null, + "location_id": null, + "product_model_uuid": null, + "process_id": null, + "location_latitude": null, + "location_longitude": null, + "location_name": null, + "location_description": null, + "location_address": null, + "product_model_name": null, + "product_model_description": null, + "product_subtype_name": null, + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "44", + "profile_id": "1", + "asset_tag_number": "D000043", + "asset_name": "gdxg", + "asset_description": "dssdsdsd", + "asset_date_created": "2025-02-20 13:36:50", + "asset_status": "inactief", + "product_model_id": null, + "location_id": null, + "product_model_uuid": null, + "process_id": null, + "location_latitude": null, + "location_longitude": null, + "location_name": null, + "location_description": null, + "location_address": null, + "product_model_name": null, + "product_model_description": null, + "product_subtype_name": null, + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "45", + "profile_id": "1", + "asset_tag_number": "P000044", + "asset_name": "pump", + "asset_description": "m1", + "asset_date_created": "2025-02-20 14:06:53", + "asset_status": "actief", + "product_model_id": null, + "location_id": null, + "product_model_uuid": null, + "process_id": null, + "location_latitude": null, + "location_longitude": null, + "location_name": null, + "location_description": null, + "location_address": null, + "product_model_name": null, + "product_model_description": null, + "product_subtype_name": null, + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "46", + "profile_id": "1", + "asset_tag_number": "P000045", + "asset_name": "pump", + "asset_description": "m1", + "asset_date_created": "2025-02-20 15:06:34", + "asset_status": "actief", + "product_model_id": null, + "location_id": null, + "product_model_uuid": null, + "process_id": null, + "location_latitude": null, + "location_longitude": null, + "location_name": null, + "location_description": null, + "location_address": null, + "product_model_name": null, + "product_model_description": null, + "product_subtype_name": null, + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "47", + "profile_id": "1", + "asset_tag_number": "P000046", + "asset_name": "pump", + "asset_description": "m1", + "asset_date_created": "2025-02-20 15:23:00", + "asset_status": "actief", + "product_model_id": null, + "location_id": null, + "product_model_uuid": null, + "process_id": null, + "location_latitude": null, + "location_longitude": null, + "location_name": null, + "location_description": null, + "location_address": null, + "product_model_name": null, + "product_model_description": null, + "product_subtype_name": null, + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "48", + "profile_id": "1", + "asset_tag_number": "P000047", + "asset_name": "pump", + "asset_description": "m1", + "asset_date_created": "2025-02-20 15:23:05", + "asset_status": "actief", + "product_model_id": null, + "location_id": null, + "product_model_uuid": null, + "process_id": null, + "location_latitude": null, + "location_longitude": null, + "location_name": null, + "location_description": null, + "location_address": null, + "product_model_name": null, + "product_model_description": null, + "product_subtype_name": null, + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "49", + "profile_id": "1", + "asset_tag_number": "P000048", + "asset_name": "pump", + "asset_description": "m2", + "asset_date_created": "2025-02-20 15:24:47", + "asset_status": "actief", + "product_model_id": null, + "location_id": null, + "product_model_uuid": null, + "process_id": null, + "location_latitude": null, + "location_longitude": null, + "location_name": null, + "location_description": null, + "location_address": null, + "product_model_name": null, + "product_model_description": null, + "product_subtype_name": null, + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "50", + "profile_id": "1", + "asset_tag_number": "P000049", + "asset_name": "pump", + "asset_description": "m1", + "asset_date_created": "2025-02-20 15:24:55", + "asset_status": "actief", + "product_model_id": null, + "location_id": null, + "product_model_uuid": null, + "process_id": null, + "location_latitude": null, + "location_longitude": null, + "location_name": null, + "location_description": null, + "location_address": null, + "product_model_name": null, + "product_model_description": null, + "product_subtype_name": null, + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "51", + "profile_id": "1", + "asset_tag_number": "P000050", + "asset_name": "pump", + "asset_description": "m1", + "asset_date_created": "2025-02-20 15:27:36", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "2147483647", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "52", + "profile_id": "1", + "asset_tag_number": "P000051", + "asset_name": "pump", + "asset_description": "m1", + "asset_date_created": "2025-02-20 15:29:41", + "asset_status": "actief", + "product_model_id": null, + "location_id": null, + "product_model_uuid": null, + "process_id": null, + "location_latitude": null, + "location_longitude": null, + "location_name": null, + "location_description": null, + "location_address": null, + "product_model_name": null, + "product_model_description": null, + "product_subtype_name": null, + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "53", + "profile_id": "1", + "asset_tag_number": "P000052", + "asset_name": "pump", + "asset_description": "m1", + "asset_date_created": "2025-02-20 15:30:53", + "asset_status": "actief", + "product_model_id": null, + "location_id": null, + "product_model_uuid": null, + "process_id": null, + "location_latitude": null, + "location_longitude": null, + "location_name": null, + "location_description": null, + "location_address": null, + "product_model_name": null, + "product_model_description": null, + "product_subtype_name": null, + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "54", + "profile_id": "1", + "asset_tag_number": "P000053", + "asset_name": "pump", + "asset_description": "m1", + "asset_date_created": "2025-02-20 15:31:48", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "2147483647", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "55", + "profile_id": "1", + "asset_tag_number": "P000054", + "asset_name": "pump", + "asset_description": "m1", + "asset_date_created": "2025-02-20 15:36:19", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "2147483647", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "56", + "profile_id": "1", + "asset_tag_number": "P000055", + "asset_name": "pump", + "asset_description": "m1", + "asset_date_created": "2025-02-20 15:42:37", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "2147483647", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "57", + "profile_id": "1", + "asset_tag_number": "P000056", + "asset_name": "pump", + "asset_description": "m2", + "asset_date_created": "2025-02-20 15:43:46", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "2147483647", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "58", + "profile_id": "1", + "asset_tag_number": "P000057", + "asset_name": "pump", + "asset_description": "m1", + "asset_date_created": "2025-02-20 15:46:10", + "asset_status": "actief", + "product_model_id": null, + "location_id": null, + "product_model_uuid": null, + "process_id": null, + "location_latitude": null, + "location_longitude": null, + "location_name": null, + "location_description": null, + "location_address": null, + "product_model_name": null, + "product_model_description": null, + "product_subtype_name": null, + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "59", + "profile_id": "1", + "asset_tag_number": "P000058", + "asset_name": "pump", + "asset_description": "m1", + "asset_date_created": "2025-02-20 15:46:38", + "asset_status": "actief", + "product_model_id": "6", + "location_id": "1", + "product_model_uuid": "123456789", + "process_id": "2147483647", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Vegabar 14", + "product_model_description": "vegabar 14", + "product_subtype_name": "pressure", + "profile_name": "Watersystemen", + "process_name": null + }, + { + "asset_id": "60", + "profile_id": "1", + "asset_tag_number": "P000059", + "asset_name": "sensor", + "asset_description": "PT110", + "asset_date_created": "2025-02-25 15:25:01", + "asset_status": "actief", + "product_model_id": "1", + "location_id": "1", + "product_model_uuid": "b02b3a50-3f36-4ab0-9479-84de2fca9cb1", + "process_id": "1", + "location_latitude": "51.6314", + "location_longitude": "4.708", + "location_name": "RWZI Nieuwveer", + "location_description": null, + "location_address": null, + "product_model_name": "Macbook Air 12", + "product_model_description": null, + "product_subtype_name": "Laptop", + "profile_name": "Watersystemen", + "process_name": "Voorbezinking" + } + ] +} \ No newline at end of file diff --git a/src/configs/index.js b/src/configs/index.js index ff85a92..72e867f 100644 --- a/src/configs/index.js +++ b/src/configs/index.js @@ -47,30 +47,30 @@ class ConfigManager { return fs.existsSync(configPath); } - createEndpoint(nodeName) { - try { - // Load the config for this node - const config = this.getConfig(nodeName); - - // Convert config to JSON - const configJSON = JSON.stringify(config, null, 2); + createEndpoint(nodeName) { + try { + // Load the config for this node + const config = this.getConfig(nodeName); - // Assemble the complete script - return ` - // Create the namespace structure - window.EVOLV = window.EVOLV || {}; - window.EVOLV.nodes = window.EVOLV.nodes || {}; - window.EVOLV.nodes.${nodeName} = window.EVOLV.nodes.${nodeName} || {}; + // Convert config to JSON + const configJSON = JSON.stringify(config, null, 2); - // Inject the pre-loaded config data directly into the namespace - window.EVOLV.nodes.${nodeName}.config = ${configJSON}; + // Assemble the complete script + return ` + // Create the namespace structure + window.EVOLV = window.EVOLV || {}; + window.EVOLV.nodes = window.EVOLV.nodes || {}; + window.EVOLV.nodes.${nodeName} = window.EVOLV.nodes.${nodeName} || {}; - console.log('${nodeName} config loaded and endpoint created'); - `; - } catch (error) { - throw new Error(`Failed to create endpoint for '${nodeName}': ${error.message}`); - } + // Inject the pre-loaded config data directly into the namespace + window.EVOLV.nodes.${nodeName}.config = ${configJSON}; + + console.log('${nodeName} config loaded and endpoint created'); + `; + } catch (error) { + throw new Error(`Failed to create endpoint for '${nodeName}': ${error.message}`); } + } } module.exports = ConfigManager; \ No newline at end of file From e5c98b7d306b364b56942a336a5397bbbd309c57 Mon Sep 17 00:00:00 2001 From: znetsixe <73483679+znetsixe@users.noreply.github.com> Date: Fri, 7 Nov 2025 15:09:35 +0100 Subject: [PATCH 05/10] removed some old comments, added thresholds for safeguard --- src/configs/pumpingStation.json | 24 ++++++++++++++++++++++++ src/helper/menuUtils.js | 5 ----- src/helper/menuUtils_DEPRECATED.js | 6 +----- src/measurements/Measurement.js | 2 +- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/configs/pumpingStation.json b/src/configs/pumpingStation.json index d6a0668..42bf044 100644 --- a/src/configs/pumpingStation.json +++ b/src/configs/pumpingStation.json @@ -478,6 +478,30 @@ "min": 0, "description": "Allowable error between inflow and outflow before adjustments are triggered (m3/h)." } + }, + "thresholdLowVolume": { + "default": 10, + "rules": { + "type": "number", + "min": 0, + "description": "Volume threshold (%) below which the station will shut down pumps to prevent dry running." + } + }, + "thresholdHighVolume": { + "default": 90, + "rules": { + "type": "number", + "min": 0, + "description": "Volume threshold (%) above which the station will trigger high level alarms." + } + }, + "timeThreshholdSeconds": { + "default": 120, + "rules": { + "type": "number", + "min": 0, + "description": "Time threshold (seconds) used in volume-based safety checks." + } } }, "alarms": { diff --git a/src/helper/menuUtils.js b/src/helper/menuUtils.js index ebed6b8..4cb6b84 100644 --- a/src/helper/menuUtils.js +++ b/src/helper/menuUtils.js @@ -180,7 +180,6 @@ async apiCall(node) { // Only add tagCode to URL if it exists if (tagCode) { apiUrl += `&asset_tag_number=${tagCode}`; - console.log('hello there'); } assetregisterAPI += apiUrl; @@ -461,10 +460,6 @@ populateModels( // Store only the metadata for the selected model node["modelMetadata"] = modelData.find((model) => model.name === selectedModel); }); - /* - console.log('hello here I am:'); - console.log(node["modelMetadata"]); -*/ }); }) diff --git a/src/helper/menuUtils_DEPRECATED.js b/src/helper/menuUtils_DEPRECATED.js index ebed6b8..95cae41 100644 --- a/src/helper/menuUtils_DEPRECATED.js +++ b/src/helper/menuUtils_DEPRECATED.js @@ -180,7 +180,6 @@ async apiCall(node) { // Only add tagCode to URL if it exists if (tagCode) { apiUrl += `&asset_tag_number=${tagCode}`; - console.log('hello there'); } assetregisterAPI += apiUrl; @@ -461,10 +460,7 @@ populateModels( // Store only the metadata for the selected model node["modelMetadata"] = modelData.find((model) => model.name === selectedModel); }); - /* - console.log('hello here I am:'); - console.log(node["modelMetadata"]); -*/ + }); }) diff --git a/src/measurements/Measurement.js b/src/measurements/Measurement.js index 761432b..0848657 100644 --- a/src/measurements/Measurement.js +++ b/src/measurements/Measurement.js @@ -113,7 +113,7 @@ class Measurement { // Create a new measurement that is the difference between two positions static createDifference(upstreamMeasurement, downstreamMeasurement) { - console.log('hello:'); + if (upstreamMeasurement.type !== downstreamMeasurement.type || upstreamMeasurement.variant !== downstreamMeasurement.variant) { throw new Error('Cannot calculate difference between different measurement types or variants'); From efe4a5f97d1eb040cbc821c7dc650b2655ba2be7 Mon Sep 17 00:00:00 2001 From: znetsixe <73483679+znetsixe@users.noreply.github.com> Date: Fri, 7 Nov 2025 15:30:24 +0100 Subject: [PATCH 06/10] update flow arrow --- src/menu/physicalPosition.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/menu/physicalPosition.js b/src/menu/physicalPosition.js index effbe96..fe167e5 100644 --- a/src/menu/physicalPosition.js +++ b/src/menu/physicalPosition.js @@ -6,9 +6,9 @@ class PhysicalPositionMenu { return { positionGroups: [ { group: 'Positional', options: [ - { value: 'upstream', label: '← Upstream', icon: '←'}, + { value: 'upstream', label: '→ Upstream', icon: '←'}, //flow is then typically left to right { value: 'atEquipment', label: '⊥ in place' , icon: '⊥' }, - { value: 'downstream', label: '→ Downstream' , icon: '→' } + { value: 'downstream', label: '← Downstream' , icon: '→' } ] } ], From 6be3bf92ef43e1f35c481a44495dfb42c3c80e8c Mon Sep 17 00:00:00 2001 From: znetsixe <73483679+znetsixe@users.noreply.github.com> Date: Mon, 10 Nov 2025 13:41:41 +0100 Subject: [PATCH 07/10] first creation of PID controller + adjustments to pumpingstation --- src/configs/pumpingStation.json | 244 +++++++++++++++++----------- src/pid/PIDController.js | 279 ++++++++++++++++++++++++++++++++ src/pid/examples.js | 87 ++++++++++ src/pid/index.js | 11 ++ 4 files changed, 527 insertions(+), 94 deletions(-) create mode 100644 src/pid/PIDController.js create mode 100644 src/pid/examples.js create mode 100644 src/pid/index.js diff --git a/src/configs/pumpingStation.json b/src/configs/pumpingStation.json index 42bf044..44a05ba 100644 --- a/src/configs/pumpingStation.json +++ b/src/configs/pumpingStation.json @@ -348,13 +348,13 @@ } }, "control": { - "controlStrategy": { - "default": "levelBased", + "mode": { + "default": "levelbased", "rules": { - "type": "enum", + "type": "string", "values": [ { - "value": "levelBased", + "value": "levelbased", "description": "Lead and lag pumps are controlled by basin level thresholds." }, { @@ -362,9 +362,21 @@ "description": "Pumps target a discharge pressure setpoint." }, { - "value": "flowTracking", + "value": "flowBased", "description": "Pumps modulate to match measured inflow or downstream demand." }, + { + "value": "percentageBased", + "description": "Pumps operate to maintain basin volume at a target percentage." + }, + { + "value":"powerBased", + "description": "Pumps are controlled based on power consumption.For example, to limit peak power usage or operate within netcongestion limits." + }, + { + "value": "hybrid", + "description": "Combines multiple control strategies for optimized operation." + }, { "value": "manual", "description": "Pumps are operated manually or by an external controller." @@ -373,94 +385,112 @@ "description": "Primary control philosophy for pump actuation." } }, - "levelSetpoints": { - "default": { - "startLeadPump": 1.2, - "stopLeadPump": 0.8, - "startLagPump": 1.8, - "stopLagPump": 1.4, - "alarmHigh": 2.3, - "alarmLow": 0.3 - }, + "allowedModes": { + "default": [ + "levelbased", + "pressurebased", + "flowbased", + "percentagebased", + "powerbased", + "manual" + ], "rules": { - "type": "object", - "description": "Level thresholds that govern pump staging and alarms (m).", - "schema": { - "startLeadPump": { - "default": 1.2, - "rules": { - "type": "number", - "description": "Level that starts the lead pump." - } - }, - "stopLeadPump": { - "default": 0.8, - "rules": { - "type": "number", - "description": "Level that stops the lead pump." - } - }, - "startLagPump": { - "default": 1.8, - "rules": { - "type": "number", - "description": "Level that starts the lag pump." - } - }, - "stopLagPump": { - "default": 1.4, - "rules": { - "type": "number", - "description": "Level that stops the lag pump." - } - }, - "alarmHigh": { - "default": 2.3, - "rules": { - "type": "number", - "description": "High level alarm threshold." - } - }, - "alarmLow": { - "default": 0.3, - "rules": { - "type": "number", - "description": "Low level alarm threshold." - } - } + "type": "set", + "itemType": "string", + "description": "List of control modes that the station is permitted to operate in." + } + }, + "levelbased": { + "thresholds": { + "default": [30,40,50,60,70,80,90], + "rules": { + "type": "array", + "description": "Each time a threshold is overwritten a new pump can start or kick into higher gear. Volume thresholds (%) in ascending order used for level-based control." + } + }, + "timeThresholdSeconds": { + "default": 120, + "rules": { + "type": "number", + "min": 0, + "description": "Duration the volume condition must persist before triggering pump actions (seconds)." } } }, - "pressureSetpoint": { - "default": 250, - "rules": { - "type": "number", - "min": 0, - "description": "Target discharge pressure when operating in pressure control (kPa)." + "pressureBased": { + "pressureSetpoint": { + "default": 1000, + "rules": { + "type": "number", + "min": 0, + "max": 5000, + "description": "Target discharge pressure when operating in pressure control (kPa)." + } } }, - "alarmDebounceSeconds": { - "default": 10, - "rules": { - "type": "number", - "min": 0, - "description": "Time a condition must persist before raising an alarm (seconds)." + "flowBased": { + "equalizationTargetPercent": { + "default": 60, + "rules": { + "type": "number", + "min": 0, + "max": 100, + "description": "Target fill percentage of the basin when operating in equalization mode." + } + }, + "flowBalanceTolerance": { + "default": 5, + "rules": { + "type": "number", + "min": 0, + "description": "Allowable error between inflow and outflow before adjustments are triggered (m3/h)." + } } }, - "equalizationTargetPercent": { - "default": 60, - "rules": { - "type": "number", - "min": 0, - "max": 100, - "description": "Target fill percentage of the basin when operating in equalization mode." + "percentageBased": { + "targetVolumePercent": { + "default": 50, + "rules": { + "type": "number", + "min": 0, + "max": 100, + "description": "Target basin volume percentage to maintain during percentage-based control." + } + }, + "tolerancePercent": { + "default": 5, + "rules": { + "type": "number", + "min": 0, + "description": "Acceptable deviation from the target volume percentage before corrective action is taken." + } } }, - "autoRestartAfterPowerLoss": { - "default": true, - "rules": { - "type": "boolean", - "description": "If true, pumps resume based on last known state after power restoration." + "powerBased": { + "maxPowerKW": { + "default": 50, + "rules": { + "type": "number", + "min": 0, + "description": "Maximum allowable power consumption for the pumping station (kW)." + } + }, + "powerControlMode": { + "default": "limit", + "rules": { + "type": "enum", + "values": [ + { + "value": "limit", + "description": "Limit pump operation to stay below the max power threshold." + }, + { + "value": "optimize", + "description": "Optimize pump scheduling to minimize power usage while meeting flow demands." + } + ], + "description": "Defines how power constraints are managed during operation." + } } }, "manualOverrideTimeoutMinutes": { @@ -470,37 +500,63 @@ "min": 0, "description": "Duration after which a manual override expires automatically (minutes)." } + } + }, + "safety": { + "enableDryRunProtection": { + "default": true, + "rules": { + "type": "boolean", + "description": "If true, pumps will be prevented from running if basin volume is too low." + } }, - "flowBalanceTolerance": { - "default": 5, + "dryRunThresholdPercent": { + "default": 2, "rules": { "type": "number", "min": 0, - "description": "Allowable error between inflow and outflow before adjustments are triggered (m3/h)." + "max": 100, + "description": "Volume percentage below which dry run protection activates." } }, - "thresholdLowVolume": { - "default": 10, + "dryRunDebounceSeconds": { + "default": 30, "rules": { "type": "number", "min": 0, - "description": "Volume threshold (%) below which the station will shut down pumps to prevent dry running." + "description": "Time the low-volume condition must persist before dry-run protection engages (seconds)." } }, - "thresholdHighVolume": { - "default": 90, + "enableOverfillProtection": { + "default": true, + "rules": { + "type": "boolean", + "description": "If true, high level alarms and shutdowns will be enforced to prevent overfilling." + } + }, + "overfillThresholdPercent": { + "default": 98, "rules": { "type": "number", "min": 0, - "description": "Volume threshold (%) above which the station will trigger high level alarms." + "max": 100, + "description": "Volume percentage above which overfill protection activates." } }, - "timeThreshholdSeconds": { + "overfillDebounceSeconds": { + "default": 30, + "rules": { + "type": "number", + "min": 0, + "description": "Time the high-volume condition must persist before overfill protection engages (seconds)." + } + }, + "timeleftToFullOrEmptyThresholdSeconds": { "default": 120, "rules": { "type": "number", "min": 0, - "description": "Time threshold (seconds) used in volume-based safety checks." + "description": "Time threshold (seconds) used to predict imminent full or empty conditions." } } }, diff --git a/src/pid/PIDController.js b/src/pid/PIDController.js new file mode 100644 index 0000000..1f07ac4 --- /dev/null +++ b/src/pid/PIDController.js @@ -0,0 +1,279 @@ +'use strict'; + +/** + * Discrete PID controller with optional derivative filtering and integral limits. + * Sample times are expressed in milliseconds to align with Node.js timestamps. + */ +class PIDController { + constructor(options = {}) { + const { + kp = 1, + ki = 0, + kd = 0, + sampleTime = 1000, + derivativeFilter = 0.15, + outputMin = Number.NEGATIVE_INFINITY, + outputMax = Number.POSITIVE_INFINITY, + integralMin = null, + integralMax = null, + derivativeOnMeasurement = true, + autoMode = true + } = options; + + this.kp = 0; + this.ki = 0; + this.kd = 0; + + this.setTunings({ kp, ki, kd }); + this.setSampleTime(sampleTime); + this.setOutputLimits(outputMin, outputMax); + this.setIntegralLimits(integralMin, integralMax); + this.setDerivativeFilter(derivativeFilter); + + this.derivativeOnMeasurement = Boolean(derivativeOnMeasurement); + this.autoMode = Boolean(autoMode); + + this.reset(); + } + + /** + * Update controller gains at runtime. + * Accepts partial objects, e.g. setTunings({ kp: 2.0 }). + */ + setTunings({ kp = this.kp, ki = this.ki, kd = this.kd } = {}) { + [kp, ki, kd].forEach((gain, index) => { + if (!Number.isFinite(gain)) { + const label = ['kp', 'ki', 'kd'][index]; + throw new TypeError(`${label} must be a finite number`); + } + }); + + this.kp = kp; + this.ki = ki; + this.kd = kd; + return this; + } + + /** + * Set the controller execution interval in milliseconds. + */ + setSampleTime(sampleTimeMs = this.sampleTime) { + if (!Number.isFinite(sampleTimeMs) || sampleTimeMs <= 0) { + throw new RangeError('sampleTime must be a positive number of milliseconds'); + } + + this.sampleTime = sampleTimeMs; + return this; + } + + /** + * Constrain controller output. + */ + setOutputLimits(min = this.outputMin, max = this.outputMax) { + if (!Number.isFinite(min) && min !== Number.NEGATIVE_INFINITY) { + throw new TypeError('outputMin must be finite or -Infinity'); + } + if (!Number.isFinite(max) && max !== Number.POSITIVE_INFINITY) { + throw new TypeError('outputMax must be finite or Infinity'); + } + if (min >= max) { + throw new RangeError('outputMin must be smaller than outputMax'); + } + + this.outputMin = min; + this.outputMax = max; + this.lastOutput = this._clamp(this.lastOutput ?? 0, this.outputMin, this.outputMax); + return this; + } + + /** + * Constrain the accumulated integral term. + */ + setIntegralLimits(min = this.integralMin ?? null, max = this.integralMax ?? null) { + if (min !== null && !Number.isFinite(min)) { + throw new TypeError('integralMin must be null or a finite number'); + } + if (max !== null && !Number.isFinite(max)) { + throw new TypeError('integralMax must be null or a finite number'); + } + if (min !== null && max !== null && min > max) { + throw new RangeError('integralMin must be smaller than integralMax'); + } + + this.integralMin = min; + this.integralMax = max; + this.integral = this._applyIntegralLimits(this.integral ?? 0); + return this; + } + + /** + * Configure exponential filter applied to the derivative term. + * Value 0 disables filtering, 1 keeps the previous derivative entirely. + */ + setDerivativeFilter(value = this.derivativeFilter ?? 0) { + if (!Number.isFinite(value) || value < 0 || value > 1) { + throw new RangeError('derivativeFilter must be between 0 and 1'); + } + + this.derivativeFilter = value; + return this; + } + + /** + * Switch between automatic (closed-loop) and manual mode. + */ + setMode(mode) { + if (mode !== 'automatic' && mode !== 'manual') { + throw new Error('mode must be either "automatic" or "manual"'); + } + + this.autoMode = mode === 'automatic'; + return this; + } + + /** + * Force a manual output (typically when in manual mode). + */ + setManualOutput(value) { + this._assertNumeric('manual output', value); + this.lastOutput = this._clamp(value, this.outputMin, this.outputMax); + return this.lastOutput; + } + + /** + * Reset dynamic state (integral, derivative memory, timestamps). + */ + reset(state = {}) { + const { + integral = 0, + lastOutput = 0, + timestamp = null + } = state; + + this.integral = this._applyIntegralLimits(Number.isFinite(integral) ? integral : 0); + this.prevError = null; + this.prevMeasurement = null; + this.lastOutput = this._clamp( + Number.isFinite(lastOutput) ? lastOutput : 0, + this.outputMin ?? Number.NEGATIVE_INFINITY, + this.outputMax ?? Number.POSITIVE_INFINITY + ); + this.lastTimestamp = Number.isFinite(timestamp) ? timestamp : null; + this.derivativeState = 0; + + return this; + } + + /** + * Execute one control loop iteration. + */ + update(setpoint, measurement, timestamp = Date.now()) { + this._assertNumeric('setpoint', setpoint); + this._assertNumeric('measurement', measurement); + this._assertNumeric('timestamp', timestamp); + + if (!this.autoMode) { + this.prevError = setpoint - measurement; + this.prevMeasurement = measurement; + this.lastTimestamp = timestamp; + return this.lastOutput; + } + + if (this.lastTimestamp !== null && (timestamp - this.lastTimestamp) < this.sampleTime) { + return this.lastOutput; + } + + const elapsedMs = this.lastTimestamp === null ? this.sampleTime : (timestamp - this.lastTimestamp); + const dtSeconds = Math.max(elapsedMs / 1000, Number.EPSILON); + + const error = setpoint - measurement; + this.integral = this._applyIntegralLimits(this.integral + error * dtSeconds); + + const derivative = this._computeDerivative({ error, measurement, dtSeconds }); + this.derivativeState = this.derivativeFilter === 0 + ? derivative + : this.derivativeState + (derivative - this.derivativeState) * (1 - this.derivativeFilter); + + const output = (this.kp * error) + (this.ki * this.integral) + (this.kd * this.derivativeState); + this.lastOutput = this._clamp(output, this.outputMin, this.outputMax); + + this.prevError = error; + this.prevMeasurement = measurement; + this.lastTimestamp = timestamp; + + return this.lastOutput; + } + + /** + * Inspect controller state for diagnostics or persistence. + */ + getState() { + return { + kp: this.kp, + ki: this.ki, + kd: this.kd, + sampleTime: this.sampleTime, + outputLimits: { min: this.outputMin, max: this.outputMax }, + integralLimits: { min: this.integralMin, max: this.integralMax }, + derivativeFilter: this.derivativeFilter, + derivativeOnMeasurement: this.derivativeOnMeasurement, + autoMode: this.autoMode, + integral: this.integral, + lastOutput: this.lastOutput, + lastTimestamp: this.lastTimestamp + }; + } + + getLastOutput() { + return this.lastOutput; + } + + _computeDerivative({ error, measurement, dtSeconds }) { + if (!(dtSeconds > 0) || !Number.isFinite(dtSeconds)) { + return 0; + } + + if (this.derivativeOnMeasurement && this.prevMeasurement !== null) { + return -(measurement - this.prevMeasurement) / dtSeconds; + } + + if (this.prevError === null) { + return 0; + } + + return (error - this.prevError) / dtSeconds; + } + + _applyIntegralLimits(value) { + if (!Number.isFinite(value)) { + return 0; + } + + let result = value; + if (this.integralMin !== null && result < this.integralMin) { + result = this.integralMin; + } + if (this.integralMax !== null && result > this.integralMax) { + result = this.integralMax; + } + return result; + } + + _assertNumeric(label, value) { + if (!Number.isFinite(value)) { + throw new TypeError(`${label} must be a finite number`); + } + } + + _clamp(value, min, max) { + if (value < min) { + return min; + } + if (value > max) { + return max; + } + return value; + } +} + +module.exports = PIDController; diff --git a/src/pid/examples.js b/src/pid/examples.js new file mode 100644 index 0000000..ea9cf63 --- /dev/null +++ b/src/pid/examples.js @@ -0,0 +1,87 @@ +const { PIDController } = require('./index'); + +console.log('=== PID CONTROLLER EXAMPLES ===\n'); +console.log('This guide shows how to instantiate, tune, and operate the PID helper.\n'); + +// ==================================== +// EXAMPLE 1: FLOW CONTROL LOOP +// ==================================== +console.log('--- Example 1: Pump speed control ---'); + +const pumpController = new PIDController({ + kp: 1.1, + ki: 0.35, + kd: 0.08, + sampleTime: 250, // ms + outputMin: 0, + outputMax: 100, + derivativeFilter: 0.2 +}); + +const pumpSetpoint = 75; // desired flow percentage +let pumpFlow = 20; +const pumpStart = Date.now(); + +for (let i = 0; i < 10; i += 1) { + const timestamp = pumpStart + (i + 1) * pumpController.sampleTime; + const controlSignal = pumpController.update(pumpSetpoint, pumpFlow, timestamp); + + // Simple first-order plant approximation + pumpFlow += (controlSignal - pumpFlow) * 0.12; + pumpFlow -= (pumpFlow - pumpSetpoint) * 0.05; // disturbance rejection + + console.log( + `Cycle ${i + 1}: output=${controlSignal.toFixed(2)}% | flow=${pumpFlow.toFixed(2)}%` + ); +} + +console.log('Pump loop state:', pumpController.getState(), '\n'); + +// ==================================== +// EXAMPLE 2: TANK LEVEL WITH MANUAL/AUTO +// ==================================== +console.log('--- Example 2: Tank level handover ---'); + +const tankController = new PIDController({ + kp: 2.0, + ki: 0.5, + kd: 0.25, + sampleTime: 400, + derivativeFilter: 0.25, + outputMin: 0, + outputMax: 1 +}).setIntegralLimits(-0.3, 0.3); + +tankController.setMode('manual'); +tankController.setManualOutput(0.4); +console.log(`Manual output locked at ${tankController.getLastOutput().toFixed(2)}\n`); + +tankController.setMode('automatic'); + +let level = 0.2; +const levelSetpoint = 0.8; +const tankStart = Date.now(); + +for (let step = 0; step < 8; step += 1) { + const timestamp = tankStart + (step + 1) * tankController.sampleTime; + const output = tankController.update(levelSetpoint, level, timestamp); + + // Integrating process with slight disturbance + level += (output - 0.5) * 0.18; + level += 0.02; // inflow bump + level = Math.max(0, Math.min(1, level)); + + console.log( + `Cycle ${step + 1}: output=${output.toFixed(3)} | level=${level.toFixed(3)}` + ); +} + +console.log('\nBest practice tips:'); +console.log(' - Call update() on a fixed interval (sampleTime).'); +console.log(' - Clamp output and integral to avoid windup.'); +console.log(' - Use setMode("manual") during maintenance or bump-less transfer.'); + +module.exports = { + pumpController, + tankController +}; diff --git a/src/pid/index.js b/src/pid/index.js new file mode 100644 index 0000000..7f2c82b --- /dev/null +++ b/src/pid/index.js @@ -0,0 +1,11 @@ +const PIDController = require('./PIDController'); + +/** + * Convenience factory for one-line instantiation. + */ +const createPidController = (options) => new PIDController(options); + +module.exports = { + PIDController, + createPidController +}; From 5df38813751899bb3fd849175b95c36474879c1e Mon Sep 17 00:00:00 2001 From: znetsixe <73483679+znetsixe@users.noreply.github.com> Date: Wed, 12 Nov 2025 17:39:39 +0100 Subject: [PATCH 08/10] added gravity function for calculating local g updated config for faster testing and changed the symbols at physical pos --- index.js | 4 +- src/configs/pumpingStation.json | 2 +- src/coolprop-node/src/index.js | 78 ++++++++++++++++++++++++++-- src/helper/gravity.js | 90 +++++++++++++++++++++++++++++++++ src/menu/physicalPosition.js | 4 +- 5 files changed, 169 insertions(+), 9 deletions(-) create mode 100644 src/helper/gravity.js diff --git a/index.js b/index.js index 0c437c8..3ca63cb 100644 --- a/index.js +++ b/index.js @@ -14,6 +14,7 @@ const validation = require('./src/helper/validationUtils.js'); const configUtils = require('./src/helper/configUtils.js'); const assertions = require('./src/helper/assertionUtils.js') const coolprop = require('./src/coolprop-node/src/index.js'); +const gravity = require('./src/helper/gravity.js') // Domain-specific modules const { MeasurementContainer } = require('./src/measurements/index.js'); @@ -44,5 +45,6 @@ module.exports = { convert, MenuManager, childRegistrationUtils, - loadCurve + loadCurve, + gravity }; diff --git a/src/configs/pumpingStation.json b/src/configs/pumpingStation.json index 44a05ba..d8ea505 100644 --- a/src/configs/pumpingStation.json +++ b/src/configs/pumpingStation.json @@ -409,7 +409,7 @@ } }, "timeThresholdSeconds": { - "default": 120, + "default": 5, "rules": { "type": "number", "min": 0, diff --git a/src/coolprop-node/src/index.js b/src/coolprop-node/src/index.js index c5b98ed..e5b2890 100644 --- a/src/coolprop-node/src/index.js +++ b/src/coolprop-node/src/index.js @@ -3,11 +3,61 @@ const customRefs = require('./refData.js'); class CoolPropWrapper { constructor() { + this.initialized = false; this.defaultRefrigerant = null; this.defaultTempUnit = 'K'; // K, C, F this.defaultPressureUnit = 'Pa' // Pa, kPa, bar, psi this.customRef = false; + this.PropsSI = this._propsSI.bind(this); + + + // 🔹 Wastewater correction options (defaults) + this._ww = { + enabled: true, + tss_g_per_L: 3.5, // default MLSS / TSS + density_k: 2e-4, // +0.02% per g/L + viscosity_k: 0.07, // +7% per g/L (clamped) + viscosity_max_gpl: 4 // cap effect at 4 g/L + }; + + this._initPromise = null; + this._autoInit({ refrigerant: 'Water' }); + + } + + _isWastewaterFluid(fluidRaw) { + if (!fluidRaw) return false; + const token = String(fluidRaw).trim().toLowerCase(); + return token === 'wastewater' || token.startsWith('wastewater:'); + } + + _parseWastewaterFluid(fluidRaw) { + if (!this._isWastewaterFluid(fluidRaw)) return null; + const ww = { ...this._ww }; + const [, tail] = String(fluidRaw).split(':'); + if (tail) { + tail.split(',').forEach(pair => { + const [key, value] = pair.split('=').map(s => s.trim().toLowerCase()); + if (key === 'tss' && !Number.isNaN(Number(value))) { + ww.tss_g_per_L = Number(value); + } + }); + } + return ww; + } + + _applyWastewaterCorrection(outputKey, baseValue, ww) { + if (!Number.isFinite(baseValue) || !ww || !ww.enabled) return baseValue; + switch (outputKey.toUpperCase()) { + case 'D': // density + return baseValue * (1 + ww.density_k * ww.tss_g_per_L); + case 'V': // viscosity + const effTss = Math.min(ww.tss_g_per_L, ww.viscosity_max_gpl); + return baseValue * (1 + ww.viscosity_k * effTss); + default: + return baseValue; + } } // Temperature conversion helpers @@ -407,13 +457,31 @@ class CoolPropWrapper { } } - // Direct access to CoolProp functions - async getPropsSI() { - if(!this.initialized) { - await coolprop.init(); + _autoInit(defaults) { + if (!this._initPromise) { + this._initPromise = this.init(defaults); } - return coolprop.PropsSI; + return this._initPromise; } + + _propsSI(outputKey, inKey1, inVal1, inKey2, inVal2, fluidRaw) { + if (!this.initialized) { + // Start init if no one else asked yet + this._autoInit({ refrigerant: this.defaultRefrigerant || 'Water' }); + throw new Error('CoolProp is still warming up, retry PropsSI in a moment'); + } + const ww = this._parseWastewaterFluid(fluidRaw); + const fluid = ww ? 'Water' : (this.customRefString || fluidRaw); + const baseValue = coolprop.PropsSI(outputKey, inKey1, inVal1, inKey2, inVal2, fluid); + return ww ? this._applyWastewaterCorrection(outputKey, baseValue, ww) : baseValue; + } + + //Access to coolprop + async getPropsSI() { + await this._ensureInit({ refrigerant: this.defaultRefrigerant || 'Water' }); + return this.PropsSI; + } + } module.exports = new CoolPropWrapper(); diff --git a/src/helper/gravity.js b/src/helper/gravity.js new file mode 100644 index 0000000..b6fb568 --- /dev/null +++ b/src/helper/gravity.js @@ -0,0 +1,90 @@ +/** + * Gravity calculations based on WGS-84 ellipsoid model. + * Author: Rene de Ren (Waterschap Brabantse Delta) + * License: EUPL-1.2 + */ + +class Gravity { + constructor() { + // Standard (conventional) gravity at 45° latitude, sea level + this.g0 = 9.80665; // m/s² + } + + /** + * Returns standard gravity (constant) + * @returns {number} gravity in m/s² + */ + getStandardGravity() { + return this.g0; + } + + /** + * Computes local gravity based on latitude and elevation. + * Formula: WGS-84 normal gravity (Somigliana) + * @param {number} latitudeDeg Latitude in degrees (−90 → +90) + * @param {number} elevationM Elevation above sea level [m] + * @returns {number} gravity in m/s² + */ + getLocalGravity(latitudeDeg, elevationM = 0) { + const phi = (latitudeDeg * Math.PI) / 180; + const sinPhi = Math.sin(phi); + const sin2 = sinPhi * sinPhi; + const sin2_2phi = Math.sin(2 * phi) ** 2; + + // WGS-84 normal gravity on the ellipsoid + const gSurface = + 9.780327 * (1 + 0.0053024 * sin2 - 0.0000058 * sin2_2phi); + + // Free-air correction for elevation (~ −3.086×10⁻⁶ m/s² per m) + const gLocal = gSurface - 3.086e-6 * elevationM; + return gLocal; + } + + /** + * Calculates hydrostatic pressure difference (ΔP = ρ g h) + * @param {number} density Fluid density [kg/m³] + * @param {number} heightM Height difference [m] + * @param {number} latitudeDeg Latitude (for local g) + * @param {number} elevationM Elevation (for local g) + * @returns {number} Pressure difference [Pa] + */ + pressureHead(density, heightM, latitudeDeg = 45, elevationM = 0) { + const g = this.getLocalGravity(latitudeDeg, elevationM); + return density * g * heightM; + } + + /** + * Calculates weight force (F = m g) + * @param {number} massKg Mass [kg] + * @param {number} latitudeDeg Latitude (for local g) + * @param {number} elevationM Elevation (for local g) + * @returns {number} Force [N] + */ + weightForce(massKg, latitudeDeg = 45, elevationM = 0) { + const g = this.getLocalGravity(latitudeDeg, elevationM); + return massKg * g; + } +} + +module.exports = new Gravity(); + + +/* +const gravity = gravity; + +// Standard gravity +console.log('g₀ =', gravity.getStandardGravity(), 'm/s²'); + +// Local gravity (Breda ≈ 51.6° N, 3 m elevation) +console.log('g @ Breda =', gravity.getLocalGravity(51.6, 3).toFixed(6), 'm/s²'); + +// Head pressure for 5 m water column at Breda +console.log( + 'ΔP =', + gravity.pressureHead(1000, 5, 51.6, 3).toFixed(1), + 'Pa' +); + +// Weight of 1 kg mass at Breda +console.log('Weight =', gravity.weightForce(1, 51.6, 3).toFixed(6), 'N'); +*/ \ No newline at end of file diff --git a/src/menu/physicalPosition.js b/src/menu/physicalPosition.js index fe167e5..aab35e9 100644 --- a/src/menu/physicalPosition.js +++ b/src/menu/physicalPosition.js @@ -6,9 +6,9 @@ class PhysicalPositionMenu { return { positionGroups: [ { group: 'Positional', options: [ - { value: 'upstream', label: '→ Upstream', icon: '←'}, //flow is then typically left to right + { value: 'upstream', label: '→ Upstream', icon: '→'}, //flow is then typically left to right { value: 'atEquipment', label: '⊥ in place' , icon: '⊥' }, - { value: 'downstream', label: '← Downstream' , icon: '→' } + { value: 'downstream', label: '← Downstream' , icon: '←' } ] } ], From f2c9134b64e86138ca3dcb66f87e1478f2782b2a Mon Sep 17 00:00:00 2001 From: znetsixe <73483679+znetsixe@users.noreply.github.com> Date: Thu, 13 Nov 2025 19:39:48 +0100 Subject: [PATCH 09/10] Added new menu jsons --- datasets/assetData/index.js | 89 ++ datasets/assetData/machine.json | 21 + datasets/assetData/measurement.json | 52 + datasets/assetData/modelData/ECDV.json | 16 + .../modelData/hidrostal-C5-D03R-SHN1.json | 838 +++++++++++++ .../modelData/hidrostal-H05K-S03R.json | 1062 +++++++++++++++++ datasets/assetData/modelData/index.js | 124 ++ datasets/assetData/valve.json | 27 + index.js | 6 +- src/configs/rotatingMachine.json | 2 + src/menu/asset.js | 495 +++++--- src/menu/asset_DEPRECATED.js | 243 ++++ src/menu/index.js | 24 +- 13 files changed, 2851 insertions(+), 148 deletions(-) create mode 100644 datasets/assetData/index.js create mode 100644 datasets/assetData/machine.json create mode 100644 datasets/assetData/measurement.json create mode 100644 datasets/assetData/modelData/ECDV.json create mode 100644 datasets/assetData/modelData/hidrostal-C5-D03R-SHN1.json create mode 100644 datasets/assetData/modelData/hidrostal-H05K-S03R.json create mode 100644 datasets/assetData/modelData/index.js create mode 100644 datasets/assetData/valve.json create mode 100644 src/menu/asset_DEPRECATED.js diff --git a/datasets/assetData/index.js b/datasets/assetData/index.js new file mode 100644 index 0000000..af0ccda --- /dev/null +++ b/datasets/assetData/index.js @@ -0,0 +1,89 @@ +const fs = require('fs'); +const path = require('path'); + +class AssetCategoryManager { + constructor(relPath = '.') { + this.assetDir = path.resolve(__dirname, relPath); + this.cache = new Map(); + } + + getCategory(softwareType) { + if (!softwareType) { + throw new Error('softwareType is required'); + } + + if (this.cache.has(softwareType)) { + return this.cache.get(softwareType); + } + + const filePath = path.resolve(this.assetDir, `${softwareType}.json`); + if (!fs.existsSync(filePath)) { + throw new Error(`Asset data '${softwareType}' not found in ${this.assetDir}`); + } + + const raw = fs.readFileSync(filePath, 'utf8'); + const parsed = JSON.parse(raw); + this.cache.set(softwareType, parsed); + return parsed; + } + + hasCategory(softwareType) { + const filePath = path.resolve(this.assetDir, `${softwareType}.json`); + return fs.existsSync(filePath); + } + + listCategories({ withMeta = false } = {}) { + const files = fs.readdirSync(this.assetDir, { withFileTypes: true }); + + return files + .filter( + (entry) => + entry.isFile() && + entry.name.endsWith('.json') && + entry.name !== 'index.json' && + entry.name !== 'assetData.json' + ) + .map((entry) => path.basename(entry.name, '.json')) + .map((name) => { + if (!withMeta) { + return name; + } + + const data = this.getCategory(name); + return { + softwareType: data.softwareType || name, + label: data.label || name, + file: `${name}.json` + }; + }); + } + + searchCategories(query) { + const term = (query || '').trim().toLowerCase(); + if (!term) { + return []; + } + + return this.listCategories({ withMeta: true }).filter( + ({ softwareType, label }) => + softwareType.toLowerCase().includes(term) || + label.toLowerCase().includes(term) + ); + } + + clearCache() { + this.cache.clear(); + } +} + +const assetCategoryManager = new AssetCategoryManager(); + +module.exports = { + AssetCategoryManager, + assetCategoryManager, + getCategory: (softwareType) => assetCategoryManager.getCategory(softwareType), + listCategories: (options) => assetCategoryManager.listCategories(options), + searchCategories: (query) => assetCategoryManager.searchCategories(query), + hasCategory: (softwareType) => assetCategoryManager.hasCategory(softwareType), + clearCache: () => assetCategoryManager.clearCache() +}; diff --git a/datasets/assetData/machine.json b/datasets/assetData/machine.json new file mode 100644 index 0000000..04c8563 --- /dev/null +++ b/datasets/assetData/machine.json @@ -0,0 +1,21 @@ +{ + "id": "machine", + "label": "machine", + "softwareType": "machine", + "suppliers": [ + { + "id": "hidrostal", + "name": "Hidrostal", + "types": [ + { + "id": "pump-centrifugal", + "name": "Centrifugal", + "models": [ + { "id": "hidrostal-H05K-S03R", "name": "hidrostal-H05K-S03R", "units": ["l/s","m3/h"] }, + { "id": "hidrostal-C5-D03R-SHN1", "name": "hidrostal-C5-D03R-SHN1", "units": ["l/s"] } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/datasets/assetData/measurement.json b/datasets/assetData/measurement.json new file mode 100644 index 0000000..04e34ad --- /dev/null +++ b/datasets/assetData/measurement.json @@ -0,0 +1,52 @@ +{ + "id": "sensor", + "label": "Sensor", + "softwareType": "measurement", + "suppliers": [ + { + "id": "vega", + "name": "Vega", + "types": [ + { + "id": "temperature", + "name": "Temperature", + "models": [ + { "id": "vega-temp-10", "name": "VegaTemp 10", "units": ["degC", "degF"] }, + { "id": "vega-temp-20", "name": "VegaTemp 20", "units": ["degC", "degF"] } + ] + }, + { + "id": "pressure", + "name": "Pressure", + "models": [ + { "id": "vega-pressure-10", "name": "VegaPressure 10", "units": ["bar", "mbar", "psi"] }, + { "id": "vega-pressure-20", "name": "VegaPressure 20", "units": ["bar", "mbar", "psi"] } + ] + }, + { + "id": "flow", + "name": "Flow", + "models": [ + { "id": "vega-flow-10", "name": "VegaFlow 10", "units": ["m3/h", "gpm", "l/min"] }, + { "id": "vega-flow-20", "name": "VegaFlow 20", "units": ["m3/h", "gpm", "l/min"] } + ] + }, + { + "id": "level", + "name": "Level", + "models": [ + { "id": "vega-level-10", "name": "VegaLevel 10", "units": ["m", "ft", "mm"] }, + { "id": "vega-level-20", "name": "VegaLevel 20", "units": ["m", "ft", "mm"] } + ] + }, + { + "id": "oxygen", + "name": "Quantity (oxygen)", + "models": [ + { "id": "vega-oxy-10", "name": "VegaOxySense 10", "units": ["g/m3", "mol/m3"] } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/datasets/assetData/modelData/ECDV.json b/datasets/assetData/modelData/ECDV.json new file mode 100644 index 0000000..895c38e --- /dev/null +++ b/datasets/assetData/modelData/ECDV.json @@ -0,0 +1,16 @@ +{ + "1.204": { + "125": { + "x": [0,10,20,30,40,50,60,70,80,90,100], + "y": [0,18,50,95,150,216,337,564,882,1398,1870] + }, + "150": { + "x": [0,10,20,30,40,50,60,70,80,90,100], + "y": [0,25,73,138,217,314,490,818,1281,2029,2715] + }, + "400": { + "x": [0,10,20,30,40,50,60,70,80,90,100], + "y": [0,155,443,839,1322,1911,2982,4980,7795,12349,16524] + } + } +} \ No newline at end of file diff --git a/datasets/assetData/modelData/hidrostal-C5-D03R-SHN1.json b/datasets/assetData/modelData/hidrostal-C5-D03R-SHN1.json new file mode 100644 index 0000000..2ea753b --- /dev/null +++ b/datasets/assetData/modelData/hidrostal-C5-D03R-SHN1.json @@ -0,0 +1,838 @@ +{ + "np": { + "400": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5953611390998625, + 1.6935085477165994, + 3.801139124304824, + 7.367829525776738, + 12.081735423116616 + ] + }, + "500": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.8497068236812997, + 3.801139124304824, + 7.367829525776738, + 12.081735423116616 + ] + }, + "600": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.7497197821018213, + 3.801139124304824, + 7.367829525776738, + 12.081735423116616 + ] + }, + "700": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.788320579602724, + 3.9982668237045984, + 7.367829525776738, + 12.081735423116616 + ] + }, + "800": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.7824519364844427, + 3.9885060367793064, + 7.367829525776738, + 12.081735423116616 + ] + }, + "900": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6934482683506376, + 3.9879559558537054, + 7.367829525776738, + 12.081735423116616 + ] + }, + "1000": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6954385513069579, + 4.0743508382926795, + 7.422392692482345, + 12.081735423116616 + ] + }, + "1100": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 4.160745720731654, + 7.596626714476177, + 12.081735423116616 + ] + }, + "1200": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 4.302551231007837, + 7.637247864947884, + 12.081735423116616 + ] + }, + "1300": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 4.37557913990704, + 7.773442147000839, + 12.081735423116616 + ] + }, + "1400": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 4.334434337766139, + 7.940911352646818, + 12.081735423116616 + ] + }, + "1500": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 4.2327206586037995, + 8.005238800611183, + 12.254836577088351 + ] + }, + "1600": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 4.195405588464695, + 7.991827302945298, + 12.423663269044452 + ] + }, + "1700": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 14.255458319309813, + 8.096768422220196, + 12.584668380908582 + ] + }, + "1800": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 31.54620347513727, + 12.637080520201405 + ] + }, + "1900": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 8.148423429611098, + 12.74916725120127 + ] + }, + "2000": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 8.146439484120116, + 12.905178964345618 + ] + }, + "2100": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 8.149576025637684, + 13.006940917309247 + ] + }, + "2200": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 8.126246430368305, + 13.107503837410825 + ] + }, + "2300": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 8.104379361635342, + 13.223235973280122 + ] + }, + "2400": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 8.135190080423746, + 13.36128347785936 + ] + }, + "2500": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 7.981219508598527, + 13.473697427231842 + ] + }, + "2600": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 7.863899404441271, + 13.50303289156837 + ] + }, + "2700": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 7.658860522528131, + 13.485230880073107 + ] + }, + "2800": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 7.44407948309266, + 13.446135725634615 + ] + }, + "2900": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 0.5522732775894703, + 1.6920721090317592, + 3.8742719210788685, + 7.44407948309266, + 13.413693596332184 + ] + } + }, + "nq": { + "400": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 7.6803204433986965, + 25.506609120436963, + 35.4, + 44.4, + 52.5 + ] + }, + "500": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 22.622804921188227, + 35.4, + 44.4, + 52.5 + ] + }, + "600": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 19.966301579194372, + 35.4, + 44.4, + 52.5 + ] + }, + "700": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 17.430763940163832, + 33.79508340848005, + 44.4, + 52.5 + ] + }, + "800": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 14.752921911234477, + 31.71885034449889, + 44.4, + 52.5 + ] + }, + "900": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 11.854693031181021, + 29.923046639543475, + 44.4, + 52.5 + ] + }, + "1000": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.549433913822687, + 26.734189128096668, + 43.96760750800311, + 52.5 + ] + }, + "1100": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 26.26933164936586, + 42.23523193272671, + 52.5 + ] + }, + "1200": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 24.443114637042832, + 40.57167959798151, + 52.5 + ] + }, + "1300": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 22.41596168949836, + 39.04561852479495, + 52.5 + ] + }, + "1400": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 20.276864821170303, + 37.557663261443224, + 52.252852231224054 + ] + }, + "1500": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 18.252772588147742, + 35.9974418607538, + 50.68604059588987 + ] + }, + "1600": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 16.31441663648616, + 34.51170378091407, + 49.20153034100798 + ] + }, + "1700": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 14.255458319309813, + 33.043410795291045, + 47.820213744181245 + ] + }, + "1800": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 31.54620347513727, + 46.51705619739449 + ] + }, + "1900": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 29.986013742375484, + 45.29506741639918 + ] + }, + "2000": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 28.432646044605782, + 44.107822395271945 + ] + }, + "2100": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 26.892634464336055, + 42.758175515158776 + ] + }, + "2200": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 25.270679127870263, + 41.467063889795895 + ] + }, + "2300": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 23.531132157718837, + 40.293041104955826 + ] + }, + "2400": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 21.815645106750623, + 39.03109248860755 + ] + }, + "2500": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 20.34997949463564, + 37.71320701654063 + ] + }, + "2600": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 18.81710568651804, + 36.35563657017404 + ] + }, + "2700": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 17.259072160217805, + 35.02979557646653 + ] + }, + "2800": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 16, + 33.74372254979665 + ] + }, + "2900": { + "x": [ + 0, + 25.510204081632654, + 51.020408163265309, + 76.530612244897952, + 100 + ], + "y": [ + 6.4, + 9.500000000000002, + 12.7, + 16, + 32.54934541379723 + ] + } + } +} diff --git a/datasets/assetData/modelData/hidrostal-H05K-S03R.json b/datasets/assetData/modelData/hidrostal-H05K-S03R.json new file mode 100644 index 0000000..3c381a7 --- /dev/null +++ b/datasets/assetData/modelData/hidrostal-H05K-S03R.json @@ -0,0 +1,1062 @@ +{ + "np": { + "700": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 12.962460720759278, + 20.65443723573673, + 31.029351002816465, + 44.58926412111886, + 62.87460150792057 + ] + }, + "800": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 13.035157335397209, + 20.74906989186132, + 31.029351002816465, + 44.58926412111886, + 62.87460150792057 + ] + }, + "900": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 13.064663380158798, + 20.927197054134297, + 31.107126521989933, + 44.58926412111886, + 62.87460150792057 + ] + }, + "1000": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 13.039271391128953, + 21.08680188366637, + 31.30899920405947, + 44.58926412111886, + 62.87460150792057 + ] + }, + "1100": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 12.940075520572446, + 21.220547481589954, + 31.51468295656385, + 44.621326083982, + 62.87460150792057 + ] + }, + "1200": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 12.784378070157494, + 21.287467135615458, + 31.736145492247378, + 44.833460637148086, + 62.87460150792057 + ] + }, + "1300": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 12.586915243939579, + 21.276682281369446, + 31.930487772749828, + 45.09147841519212, + 62.87460150792057 + ] + }, + "1400": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 12.072531459639976, + 21.236263402754997, + 31.98957228629009, + 45.343639823277805, + 62.948551456696194 + ] + }, + "1500": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 11.498673648884504, + 20.996631954252724, + 31.954252725886462, + 45.54353714625641, + 63.22528016894755 + ] + }, + "1600": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 52.14679487594751, + 20.746724065725342, + 31.960270693111905, + 45.6989826531509, + 63.50000000000001 + ] + }, + "1700": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 10.785741081439639, + 20.410520957192535, + 31.950197200275465, + 45.844022293894504, + 63.800401477703126 + ] + }, + "1800": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 10.26507140279083, + 20.02134876415971, + 31.90474593035864, + 45.99882821699525, + 64.10190222175436 + ] + }, + "1900": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.844493687783078, + 19.615126745440445, + 31.784477814504157, + 46.121518686299474, + 64.37205899496851 + ] + }, + "2000": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.42546845395214, + 19.224613161465353, + 31.3852031134771, + 46.15771544706397, + 64.55065634962911 + ] + }, + "2100": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.977806634186596, + 18.777333452839002, + 31.231492686456505, + 46.13420576468383, + 64.64634734417953 + ] + }, + "2200": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.5551220832516, + 18.192271683023783, + 31.21886730567425, + 46.10526642440768, + 64.7459373335406 + ] + }, + "2300": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.224790390000274, + 17.635073073073073, + 30.69719637959011, + 46.04336860563764, + 64.87880030950727 + ] + }, + "2400": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 17.235714899207412, + 30.206677994537266, + 45.90194286632148, + 65.00133289948793 + ] + }, + "2500": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 16.699519153953943, + 29.81226369335321, + 45.68999350609509, + 65.08194121217663 + ] + }, + "2600": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 16.128295133509337, + 29.372650465392372, + 45.440269896240885, + 65.1262338514688 + ] + }, + "2700": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 15.655831107176521, + 28.888887637256676, + 45.14580957087996, + 65.13308230125698 + ] + }, + "2800": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 15.218098933011891, + 28.362864023341317, + 44.807426250648106, + 65.10511931024406 + ] + }, + "2900": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 14.727036592419225, + 27.800257499369994, + 44.41688206158469, + 65.01783815190142 + ] + }, + "3000": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 14.220778455429796, + 27.231492686456505, + 44.05531409059111, + 64.84454626378002 + ] + }, + "3100": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 13.791032481569887, + 26.655487058053755, + 43.47550152847766, + 64.61338781598111 + ] + }, + "3200": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 13.426327986363882, + 57.998168647814666, + 42.997354839160536, + 64.33911122026377 + ] + }, + "3300": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 13.426327986363882, + 53.35067019159144, + 42.48429874246399, + 64.03769740244357 + ] + }, + "3400": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 13.426327986363882, + 24.605489108239045, + 41.93544657954916, + 63.75332312922636 + ] + }, + "3500": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 13.426327986363882, + 24.02776812223464, + 41.3462311518563, + 63.43861799695663 + ] + }, + "3600": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 13.426327986363882, + 23.461492562203443, + 40.66666743038082, + 63.03442493367597 + ] + }, + "3700": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 13.426327986363882, + 22.83964444901582, + 39.93227924494096, + 62.58510941648396 + ] + }, + "3800": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 13.426327986363882, + 22.224853190033304, + 39.26854818553173, + 62.120049154943764 + ] + }, + "3900": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 8.219999984177646, + 13.426327986363882, + 21.72969647212158, + 38.65394379517984, + 61.64012936635131 + ] + } + }, + "nq": { + "700": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 119.13938764447377, + 150.12178608265387, + 178.82698019104356, + 202.3699313222398, + 227.06382297856618 + ] + }, + "800": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 112.59072109293984, + 148.15847460389205, + 178.82698019104356, + 202.3699313222398, + 227.06382297856618 + ] + }, + "900": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 105.6217241180404, + 144.00502117747064, + 177.15212647335034, + 202.3699313222398, + 227.06382297856618 + ] + }, + "1000": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 97.96933385655602, + 139.33203004341362, + 172.8335214963562, + 202.3699313222398, + 227.06382297856618 + ] + }, + "1100": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 89.46890733013123, + 133.63746503107248, + 168.6757638770697, + 201.51457815731206, + 227.06382297856618 + ] + }, + "1200": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 81.7102176307068, + 127.54746478805862, + 164.86083942366332, + 197.9278536516828, + 227.06382297856618 + ] + }, + "1300": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 73.54434350844221, + 121.17569010344418, + 160.74497886055957, + 194.59764221140935, + 227.06382297856618 + ] + }, + "1400": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 65.44062943901834, + 114.06019126455426, + 155.75252082246928, + 191.17149532208072, + 226.18795889319966 + ] + }, + "1500": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 58.16022827241729, + 106.8304040176964, + 150.34769411635546, + 187.41150790422392, + 223.01071026385065 + ] + }, + "1600": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 52.14679487594751, + 99.83305618056556, + 144.95937497345926, + 183.42837752248894, + 219.8652102448096 + ] + }, + "1700": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 46.01552158918748, + 93.03792449348434, + 139.34246720444983, + 179.30356404990695, + 216.85840103402688 + ] + }, + "1800": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 39.891102390431755, + 86.36278038299652, + 133.36260934920088, + 175.064220776007, + 213.87502962516638 + ] + }, + "1900": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 34.11562677513334, + 79.92122861259746, + 127.3583046971797, + 170.60370417418847, + 210.68808498795732 + ] + }, + "2000": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 28.23139893028536, + 74.27239543161777, + 121.3852031134771, + 165.77656329385528, + 207.11383345844555 + ] + }, + "2100": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 22.028625643397625, + 68.52803664413138, + 115.60209814095579, + 160.766567321836, + 203.25772755139087 + ] + }, + "2200": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 15.99225052287954, + 62.252556641890656, + 109.75606164153551, + 155.8982054779732, + 199.33642607507025 + ] + }, + "2300": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 10.057512077941812, + 56.78739880947559, + 103.28490253306859, + 150.87040317632852, + 195.3712838183181 + ] + }, + "2400": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 51.89879972018922, + 97.49411123502856, + 145.15861930790484, + 191.3102286463588 + ] + }, + "2500": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 46.49650512979005, + 92.5946794216408, + 139.3607940098774, + 187.1228895952043 + ] + }, + "2600": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 40.96259596169048, + 87.48581221097406, + 134.10330733835585, + 182.81452539519677 + ] + }, + "2700": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 35.97902929973601, + 82.22524681851449, + 129.0407272627695, + 178.46909993743765 + ] + }, + "2800": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 31.23575309746966, + 77.15557437941118, + 124.12760990251361, + 174.10587281860919 + ] + }, + "2900": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 26.43435480236642, + 72.24738368847274, + 119.17501218716764, + 169.57138175798616 + ] + }, + "3000": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 21.604008702139296, + 67.42496298247559, + 114.05531409059111, + 164.73832634582294 + ] + }, + "3100": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 16.57058014337364, + 62.68624249227209, + 109.06006424231046, + 159.88207336974 + ] + }, + "3200": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 11.584188419724269, + 57.998168647814666, + 104.24541477723719, + 155.19789417429632 + ] + }, + "3300": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 11.584188419724269, + 53.35067019159144, + 99.62632764730057, + 150.4942891087851 + ] + }, + "3400": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 11.584188419724269, + 48.8526661624979, + 95.33061875213433, + 145.56922638322362 + ] + }, + "3500": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 11.584188419724269, + 44.55023162119764, + 91.08960051646183, + 140.69583687494227 + ] + }, + "3600": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 11.584188419724269, + 40.31135640023136, + 86.51969834209932, + 136.26067387204347 + ] + }, + "3700": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 11.584188419724269, + 36.07819040356928, + 81.84472835862138, + 132.05652400156956 + ] + }, + "3800": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 11.584188419724269, + 31.85665211119843, + 77.5230204653733, + 127.71198568555374 + ] + }, + "3900": { + "x": [ + 0, + 24.59, + 49.18, + 73.77, + 100 + ], + "y": [ + 9.542570302282291, + 11.584188419724269, + 27.623838938465713, + 73.46672821834078, + 123.26611832311883 + ] + } + } +} \ No newline at end of file diff --git a/datasets/assetData/modelData/index.js b/datasets/assetData/modelData/index.js new file mode 100644 index 0000000..c4af256 --- /dev/null +++ b/datasets/assetData/modelData/index.js @@ -0,0 +1,124 @@ +const fs = require('fs'); +const path = require('path'); + +class AssetLoader { + constructor() { + this.relPath = './' + this.baseDir = path.resolve(__dirname, this.relPath); + this.cache = new Map(); // Cache loaded JSON files for better performance + } + + /** + * Load a specific curve by type + * @param {string} curveType - The curve identifier (e.g., 'hidrostal-H05K-S03R') + * @returns {Object|null} The curve data object or null if not found + */ + loadModel(modelType) { + return this.loadAsset('models', modelType); + } + + /** + * Load any asset from a specific dataset folder + * @param {string} datasetType - The dataset folder name (e.g., 'curves', 'assetData') + * @param {string} assetId - The specific asset identifier + * @returns {Object|null} The asset data object or null if not found + */ + loadAsset(datasetType, assetId) { + //const cacheKey = `${datasetType}/${assetId}`; + const cacheKey = `${assetId}`; + + + // Check cache first + if (this.cache.has(cacheKey)) { + return this.cache.get(cacheKey); + } + + try { + const filePath = path.join(this.baseDir, `${assetId}.json`); + + // Check if file exists + if (!fs.existsSync(filePath)) { + console.warn(`Asset not found: ${filePath}`); + return null; + } + + // Load and parse JSON + const rawData = fs.readFileSync(filePath, 'utf8'); + const assetData = JSON.parse(rawData); + + // Cache the result + this.cache.set(cacheKey, assetData); + + return assetData; + } catch (error) { + console.error(`Error loading asset ${cacheKey}:`, error.message); + return null; + } + } + + /** + * Get all available assets in a dataset + * @param {string} datasetType - The dataset folder name + * @returns {string[]} Array of available asset IDs + */ + getAvailableAssets(datasetType) { + try { + const datasetPath = path.join(this.baseDir, datasetType); + + if (!fs.existsSync(datasetPath)) { + return []; + } + + return fs.readdirSync(datasetPath) + .filter(file => file.endsWith('.json')) + .map(file => file.replace('.json', '')); + } catch (error) { + console.error(`Error reading dataset ${datasetType}:`, error.message); + return []; + } + } + + /** + * Clear the cache (useful for development/testing) + */ + clearCache() { + this.cache.clear(); + } +} + +// Create and export a singleton instance +const assetLoader = new AssetLoader(); + +module.exports = { + AssetLoader, + assetLoader, + // Convenience methods for backward compatibility + loadModel: (modelType) => assetLoader.loadModel(modelType), + loadAsset: (datasetType, assetId) => assetLoader.loadAsset(datasetType, assetId), + getAvailableAssets: (datasetType) => assetLoader.getAvailableAssets(datasetType) +}; + +/* +// Example usage in your scripts +const loader = new AssetLoader(); + +// Load a specific curve +const curve = loader.loadModel('hidrostal-H05K-S03R'); +if (curve) { + console.log('Model loaded:', curve); +} else { + console.log('Model not found'); +} +/* +// Load any asset from any dataset +const someAsset = loadAsset('assetData', 'some-asset-id'); + +// Get list of available models +const availableCurves = getAvailableAssets('curves'); +console.log('Available curves:', availableCurves); + +// Using the class directly for more control +const { AssetLoader } = require('./index.js'); +const customLoader = new AssetLoader(); +const data = customLoader.loadCurve('hidrostal-H05K-S03R'); +*/ \ No newline at end of file diff --git a/datasets/assetData/valve.json b/datasets/assetData/valve.json new file mode 100644 index 0000000..56ba2e1 --- /dev/null +++ b/datasets/assetData/valve.json @@ -0,0 +1,27 @@ +{ + "id": "valve", + "label": "valve", + "softwareType": "valve", + "suppliers": [ + { + "id": "binder", + "name": "Binder Engineering", + "types": [ + { + "id": "valve-gate", + "name": "Gate", + "models": [ + { "id": "binder-valve-001", "name": "ECDV", "units": ["m3/h", "gpm", "l/min"] } + ] + }, + { + "id": "valve-jet", + "name": "Jet", + "models": [ + { "id": "binder-valve-002", "name": "JCV", "units": ["m3/h", "gpm", "l/min"] } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/index.js b/index.js index 3ca63cb..97dffa3 100644 --- a/index.js +++ b/index.js @@ -26,7 +26,8 @@ const MenuManager = require('./src/menu/index.js'); const predict = require('./src/predict/predict_class.js'); const interpolation = require('./src/predict/interpolation.js'); const childRegistrationUtils = require('./src/helper/childRegistrationUtils.js'); -const { loadCurve } = require('./datasets/assetData/curves/index.js'); +const { loadCurve } = require('./datasets/assetData/curves/index.js'); //deprecated replace with load model data +const { loadModel } = require('./datasets/assetData/modelData/index.js'); // Export everything module.exports = { @@ -45,6 +46,7 @@ module.exports = { convert, MenuManager, childRegistrationUtils, - loadCurve, + loadCurve, //deprecated replace with loadModel + loadModel, gravity }; diff --git a/src/configs/rotatingMachine.json b/src/configs/rotatingMachine.json index 744d9fb..102a0b3 100644 --- a/src/configs/rotatingMachine.json +++ b/src/configs/rotatingMachine.json @@ -260,6 +260,7 @@ "statuscheck", "execmovement", "execsequence", + "flowmovement", "emergencystop", "entermaintenance" ], @@ -273,6 +274,7 @@ "default": [ "statuscheck", "execmovement", + "flowmovement", "execsequence", "emergencystop", "exitmaintenance" diff --git a/src/menu/asset.js b/src/menu/asset.js index 76933e2..b47dc4d 100644 --- a/src/menu/asset.js +++ b/src/menu/asset.js @@ -1,62 +1,83 @@ -// asset.js -const fs = require('fs'); -const path = require('path'); +const { assetCategoryManager } = require('../../datasets/assetData'); class AssetMenu { - /** Define path where to find data of assets in constructor for now */ - constructor(relPath = '../../datasets/assetData') { - this.baseDir = path.resolve(__dirname, relPath); - this.assetData = this._loadJSON('assetData'); + constructor({ manager = assetCategoryManager, softwareType = null } = {}) { + this.manager = manager; + this.softwareType = softwareType; + this.categories = this.manager + .listCategories({ withMeta: true }) + .reduce((map, meta) => { + map[meta.softwareType] = this.manager.getCategory(meta.softwareType); + return map; + }, {}); } - _loadJSON(...segments) { - const filePath = path.resolve(this.baseDir, ...segments) + '.json'; - try { - return JSON.parse(fs.readFileSync(filePath, 'utf8')); - } catch (err) { - throw new Error(`Failed to load ${filePath}: ${err.message}`); + normalizeCategory(key) { + const category = this.categories[key]; + if (!category) { + return null; } - } - - /** - * ADD THIS METHOD - * Compiles all menu data from the file system into a single nested object. - * This is run once on the server to pre-load everything. - * @returns {object} A comprehensive object with all menu options. - */ - getAllMenuData() { - // load the raw JSON once - const data = this._loadJSON('assetData'); - const allData = {}; - data.suppliers.forEach(sup => { - allData[sup.name] = {}; - sup.categories.forEach(cat => { - allData[sup.name][cat.name] = {}; - cat.types.forEach(type => { - // here: store the full array of model objects, not just names - allData[sup.name][cat.name][type.name] = type.models; - }); - }); - }); - - return allData; + return { + ...category, + label: category.label || category.softwareType || key, + suppliers: (category.suppliers || []).map((supplier) => ({ + ...supplier, + id: supplier.id || supplier.name, + types: (supplier.types || []).map((type) => ({ + ...type, + id: type.id || type.name, + models: (type.models || []).map((model) => ({ + ...model, + id: model.id || model.name, + units: model.units || [] + })) + })) + })) + }; } - /** - * Convert the static initEditor function to a string that can be served to the client - * @param {string} nodeName - The name of the node type - * @returns {string} JavaScript code as a string - */ -getClientInitCode(nodeName) { - // step 1: get the two helper strings - const htmlCode = this.getHtmlInjectionCode(nodeName); - const dataCode = this.getDataInjectionCode(nodeName); - const eventsCode = this.getEventInjectionCode(nodeName); - const saveCode = this.getSaveInjectionCode(nodeName); + resolveCategoryForNode(nodeName) { + const keys = Object.keys(this.categories); + if (keys.length === 0) { + return null; + } + if (this.softwareType && this.categories[this.softwareType]) { + return this.softwareType; + } - return ` + if (nodeName) { + const normalized = typeof nodeName === 'string' ? nodeName.toLowerCase() : nodeName; + if (normalized && this.categories[normalized]) { + return normalized; + } + } + + return keys[0]; + } + + getAllMenuData(nodeName) { + const categoryKey = this.resolveCategoryForNode(nodeName); + const selectedCategories = {}; + + if (categoryKey && this.categories[categoryKey]) { + selectedCategories[categoryKey] = this.normalizeCategory(categoryKey); + } + + return { + categories: selectedCategories, + defaultCategory: categoryKey + }; + } + + getClientInitCode(nodeName) { + const htmlCode = this.getHtmlInjectionCode(nodeName); + const dataCode = this.getDataInjectionCode(nodeName); + const eventsCode = this.getEventInjectionCode(nodeName); + const saveCode = this.getSaveInjectionCode(nodeName); + + return ` // --- AssetMenu for ${nodeName} --- window.EVOLV.nodes.${nodeName}.assetMenu = window.EVOLV.nodes.${nodeName}.assetMenu || {}; @@ -66,103 +87,276 @@ getClientInitCode(nodeName) { ${eventsCode} ${saveCode} - // wire it all up when the editor loads window.EVOLV.nodes.${nodeName}.assetMenu.initEditor = function(node) { - // ------------------ BELOW sequence is important! ------------------------------- - console.log('Initializing asset properties for ${nodeName}…'); + console.log('Initializing asset properties for ${nodeName}'); this.injectHtml(); - // load the data and wire up events - // this will populate the fields and set up the event listeners this.wireEvents(node); - // this will load the initial data into the fields - // this is important to ensure the fields are populated correctly this.loadData(node); }; `; - -} + } -getDataInjectionCode(nodeName) { + getDataInjectionCode(nodeName) { return ` - // Asset Data loader for ${nodeName} + // Asset data loader for ${nodeName} window.EVOLV.nodes.${nodeName}.assetMenu.loadData = function(node) { - const data = window.EVOLV.nodes.${nodeName}.menuData.asset; + const menuAsset = window.EVOLV.nodes.${nodeName}.menuData.asset || {}; + const categories = menuAsset.categories || {}; + const defaultCategory = menuAsset.defaultCategory || Object.keys(categories)[0] || null; const elems = { supplier: document.getElementById('node-input-supplier'), - category: document.getElementById('node-input-category'), - type: document.getElementById('node-input-assetType'), - model: document.getElementById('node-input-model'), - unit: document.getElementById('node-input-unit') + type: document.getElementById('node-input-assetType'), + model: document.getElementById('node-input-model'), + unit: document.getElementById('node-input-unit') }; - function populate(el, opts, sel) { - const old = el.value; - el.innerHTML = ''; - (opts||[]).forEach(o=>{ + + function populate(selectEl, items = [], selectedValue, mapFn, placeholderText = 'Select...') { + const previous = selectEl.value; + const mapper = typeof mapFn === 'function' + ? mapFn + : (value) => ({ value, label: value }); + + selectEl.innerHTML = ''; + + const placeholder = document.createElement('option'); + placeholder.value = ''; + placeholder.textContent = placeholderText; + placeholder.disabled = true; + placeholder.selected = true; + selectEl.appendChild(placeholder); + + items.forEach((item) => { + const option = mapper(item); + if (!option || typeof option.value === 'undefined') { + return; + } const opt = document.createElement('option'); - opt.value = o; opt.textContent = o; - el.appendChild(opt); + opt.value = option.value; + opt.textContent = option.label; + selectEl.appendChild(opt); }); - el.value = sel||""; - if(el.value!==old) el.dispatchEvent(new Event('change')); + + if (selectedValue) { + selectEl.value = selectedValue; + if (!selectEl.value) { + selectEl.value = ''; + } + } else { + selectEl.value = ''; + } + if (selectEl.value !== previous) { + selectEl.dispatchEvent(new Event('change')); + } } - // initial population - populate(elems.supplier, Object.keys(data), node.supplier); + + const resolveCategoryKey = () => { + if (node.softwareType && categories[node.softwareType]) { + return node.softwareType; + } + if (node.category && categories[node.category]) { + return node.category; + } + return defaultCategory; + }; + + const categoryKey = resolveCategoryKey(); + node.category = categoryKey; + const activeCategory = categoryKey ? categories[categoryKey] : null; + + const suppliers = activeCategory ? activeCategory.suppliers : []; + populate( + elems.supplier, + suppliers, + node.supplier, + (supplier) => ({ value: supplier.id || supplier.name, label: supplier.name }), + suppliers.length ? 'Select...' : 'No suppliers available' + ); + + const activeSupplier = suppliers.find( + (supplier) => (supplier.id || supplier.name) === node.supplier + ); + const types = activeSupplier ? activeSupplier.types : []; + populate( + elems.type, + types, + node.assetType, + (type) => ({ value: type.id || type.name, label: type.name }), + activeSupplier ? 'Select...' : 'Awaiting Supplier Selection' + ); + + const activeType = types.find( + (type) => (type.id || type.name) === node.assetType + ); + const models = activeType ? activeType.models : []; + populate( + elems.model, + models, + node.model, + (model) => ({ value: model.id || model.name, label: model.name }), + activeType ? 'Select...' : 'Awaiting Type Selection' + ); + + const activeModel = models.find( + (model) => (model.id || model.name) === node.model + ); + populate( + elems.unit, + activeModel ? activeModel.units || [] : [], + node.unit, + (unit) => ({ value: unit, label: unit }), + activeModel ? 'Select...' : activeType ? 'Awaiting Model Selection' : 'Awaiting Type Selection' + ); }; - ` + `; } -getEventInjectionCode(nodeName) { + getEventInjectionCode(nodeName) { return ` - // Asset Event wiring for ${nodeName} + // Asset event wiring for ${nodeName} window.EVOLV.nodes.${nodeName}.assetMenu.wireEvents = function(node) { - const data = window.EVOLV.nodes.${nodeName}.menuData.asset; + const menuAsset = window.EVOLV.nodes.${nodeName}.menuData.asset || {}; + const categories = menuAsset.categories || {}; + const defaultCategory = menuAsset.defaultCategory || Object.keys(categories)[0] || null; const elems = { supplier: document.getElementById('node-input-supplier'), - category: document.getElementById('node-input-category'), - type: document.getElementById('node-input-assetType'), - model: document.getElementById('node-input-model'), - unit: document.getElementById('node-input-unit') + type: document.getElementById('node-input-assetType'), + model: document.getElementById('node-input-model'), + unit: document.getElementById('node-input-unit') }; - function populate(el, opts, sel) { - const old = el.value; - el.innerHTML = ''; - (opts||[]).forEach(o=>{ + + function populate(selectEl, items = [], selectedValue, mapFn, placeholderText = 'Select...') { + const previous = selectEl.value; + const mapper = typeof mapFn === 'function' + ? mapFn + : (value) => ({ value, label: value }); + + selectEl.innerHTML = ''; + + const placeholder = document.createElement('option'); + placeholder.value = ''; + placeholder.textContent = placeholderText; + placeholder.disabled = true; + placeholder.selected = true; + selectEl.appendChild(placeholder); + + items.forEach((item) => { + const option = mapper(item); + if (!option || typeof option.value === 'undefined') { + return; + } const opt = document.createElement('option'); - opt.value = o; opt.textContent = o; - el.appendChild(opt); + opt.value = option.value; + opt.textContent = option.label; + selectEl.appendChild(opt); }); - el.value = sel||""; - if(el.value!==old) el.dispatchEvent(new Event('change')); + + if (selectedValue) { + selectEl.value = selectedValue; + if (!selectEl.value) { + selectEl.value = ''; + } + } else { + selectEl.value = ''; + } + if (selectEl.value !== previous) { + selectEl.dispatchEvent(new Event('change')); + } } - elems.supplier.addEventListener('change', ()=>{ - populate(elems.category, - elems.supplier.value? Object.keys(data[elems.supplier.value]||{}) : [], - node.category); + + const resolveCategoryKey = () => { + if (node.softwareType && categories[node.softwareType]) { + return node.softwareType; + } + if (node.category && categories[node.category]) { + return node.category; + } + return defaultCategory; + }; + + const getActiveCategory = () => { + const key = resolveCategoryKey(); + return key ? categories[key] : null; + }; + + node.category = resolveCategoryKey(); + + elems.supplier.addEventListener('change', () => { + const category = getActiveCategory(); + const supplier = category + ? category.suppliers.find( + (item) => (item.id || item.name) === elems.supplier.value + ) + : null; + const types = supplier ? supplier.types : []; + populate( + elems.type, + types, + node.assetType, + (type) => ({ value: type.id || type.name, label: type.name }), + supplier ? 'Select...' : 'Awaiting Supplier Selection' + ); + populate(elems.model, [], '', undefined, 'Awaiting Type Selection'); + populate(elems.unit, [], '', undefined, 'Awaiting Type Selection'); }); - elems.category.addEventListener('change', ()=>{ - const s=elems.supplier.value, c=elems.category.value; - populate(elems.type, - (s&&c)? Object.keys(data[s][c]||{}) : [], - node.assetType); + + elems.type.addEventListener('change', () => { + const category = getActiveCategory(); + const supplier = category + ? category.suppliers.find( + (item) => (item.id || item.name) === elems.supplier.value + ) + : null; + const type = supplier + ? supplier.types.find( + (item) => (item.id || item.name) === elems.type.value + ) + : null; + const models = type ? type.models : []; + populate( + elems.model, + models, + node.model, + (model) => ({ value: model.id || model.name, label: model.name }), + type ? 'Select...' : 'Awaiting Type Selection' + ); + populate( + elems.unit, + [], + '', + undefined, + type ? 'Awaiting Model Selection' : 'Awaiting Type Selection' + ); }); - elems.type.addEventListener('change', ()=>{ - const s=elems.supplier.value, c=elems.category.value, t=elems.type.value; - const md = (s&&c&&t)? data[s][c][t]||[] : []; - populate(elems.model, md.map(m=>m.name), node.model); - }); - elems.model.addEventListener('change', ()=>{ - const s=elems.supplier.value, c=elems.category.value, t=elems.type.value, m=elems.model.value; - const md = (s&&c&&t)? data[s][c][t]||[] : []; - const entry = md.find(x=>x.name===m); - populate(elems.unit, entry? entry.units : [], node.unit); + + elems.model.addEventListener('change', () => { + const category = getActiveCategory(); + const supplier = category + ? category.suppliers.find( + (item) => (item.id || item.name) === elems.supplier.value + ) + : null; + const type = supplier + ? supplier.types.find( + (item) => (item.id || item.name) === elems.type.value + ) + : null; + const model = type + ? type.models.find( + (item) => (item.id || item.name) === elems.model.value + ) + : null; + populate( + elems.unit, + model ? model.units || [] : [], + node.unit, + (unit) => ({ value: unit, label: unit }), + model ? 'Select...' : type ? 'Awaiting Model Selection' : 'Awaiting Type Selection' + ); }); }; - ` + `; } -/** - * Generate HTML template for asset fields - */ getHtmlTemplate() { return ` @@ -172,10 +366,6 @@ getEventInjectionCode(nodeName) { -