Compare commits

..

4 Commits

Author SHA1 Message Date
znetsixe
4a489acd89 some formatting 2025-11-06 16:47:17 +01:00
znetsixe
98cd44d3ae updated output utils bug fixes for formatting 2025-11-06 11:18:54 +01:00
znetsixe
44adfdece6 removed caps sensitivity 2025-11-05 17:15:32 +01:00
znetsixe
9ada6e2acd Added support for maintenance tracking in hours. "getMaintenanceTimeHours" default in output of machine now 2025-11-05 15:47:05 +01:00
11 changed files with 1716 additions and 93 deletions

View File

@@ -66,33 +66,6 @@
"units": ["g/m³", "mol/m³"] "units": ["g/m³", "mol/m³"]
} }
] ]
},
{
"name": "Quantity (Ammonium)",
"models": [
{
"name": "VegaAmmoniaSense 10",
"units": ["g/m³", "mol/m³"]
}
]
},
{
"name": "Quantity (NOx)",
"models": [
{
"name": "VegaNOxSense 10",
"units": ["g/m³", "mol/m³"]
}
]
},
{
"name": "Quantity (TSS)",
"models": [
{
"name": "VegaSolidsProbe",
"units": ["g/m³"]
}
]
} }
] ]
} }
@@ -110,6 +83,7 @@
{ {
"id": "hidrostal-pump-001", "id": "hidrostal-pump-001",
"name": "hidrostal-H05K-S03R", "name": "hidrostal-H05K-S03R",
"units": ["l/s"] "units": ["l/s"]
}, },
{ {

View File

@@ -1 +0,0 @@
Database connection failed: SQLSTATE[28000] [1045] Access denied for user 'pimmoe1q_rdlab'@'localhost' (using password: YES)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,229 @@
{
"success": true,
"message": "Product modellen succesvol opgehaald.",
"data": [
{
"id": "1",
"name": "Macbook Air 12",
"product_model_subtype_id": "1",
"product_model_description": null,
"vendor_id": "1",
"product_model_status": null,
"vendor_name": "Apple",
"product_subtype_name": "Laptop",
"product_model_meta": []
},
{
"id": "2",
"name": "Macbook Air 13",
"product_model_subtype_id": "1",
"product_model_description": null,
"vendor_id": "1",
"product_model_status": null,
"vendor_name": "Apple",
"product_subtype_name": "Laptop",
"product_model_meta": []
},
{
"id": "3",
"name": "AirMac 1 128 GB White",
"product_model_subtype_id": "2",
"product_model_description": null,
"vendor_id": "1",
"product_model_status": null,
"vendor_name": "Apple",
"product_subtype_name": "Desktop",
"product_model_meta": []
},
{
"id": "4",
"name": "AirMac 2 256 GB Black",
"product_model_subtype_id": "2",
"product_model_description": null,
"vendor_id": "1",
"product_model_status": null,
"vendor_name": "Apple",
"product_subtype_name": "Desktop",
"product_model_meta": []
},
{
"id": "5",
"name": "AirMac 2 256 GB White",
"product_model_subtype_id": "2",
"product_model_description": null,
"vendor_id": "1",
"product_model_status": null,
"vendor_name": "Apple",
"product_subtype_name": "Desktop",
"product_model_meta": []
},
{
"id": "6",
"name": "Vegabar 14",
"product_model_subtype_id": "3",
"product_model_description": "vegabar 14",
"vendor_id": "4",
"product_model_status": "Actief",
"vendor_name": "vega",
"product_subtype_name": "pressure",
"product_model_meta": {
"machineCurve": {
"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
]
}
},
"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
]
}
}
}
}
},
{
"id": "7",
"name": "Vegabar 10",
"product_model_subtype_id": "3",
"product_model_description": null,
"vendor_id": "4",
"product_model_status": "Actief",
"vendor_name": "vega",
"product_subtype_name": "pressure",
"product_model_meta": []
},
{
"id": "8",
"name": "VegaFlow 10",
"product_model_subtype_id": "4",
"product_model_description": null,
"vendor_id": "4",
"product_model_status": "Actief",
"vendor_name": "vega",
"product_subtype_name": "flow",
"product_model_meta": []
}
]
}

View File

@@ -47,30 +47,30 @@ class ConfigManager {
return fs.existsSync(configPath); return fs.existsSync(configPath);
} }
createEndpoint(nodeName) { createEndpoint(nodeName) {
try { try {
// Load the config for this node // Load the config for this node
const config = this.getConfig(nodeName); const config = this.getConfig(nodeName);
// Convert config to JSON
const configJSON = JSON.stringify(config, null, 2);
// Assemble the complete script // Convert config to JSON
return ` const configJSON = JSON.stringify(config, null, 2);
// Create the namespace structure
window.EVOLV = window.EVOLV || {};
window.EVOLV.nodes = window.EVOLV.nodes || {};
window.EVOLV.nodes.${nodeName} = window.EVOLV.nodes.${nodeName} || {};
// Inject the pre-loaded config data directly into the namespace // Assemble the complete script
window.EVOLV.nodes.${nodeName}.config = ${configJSON}; 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'); // Inject the pre-loaded config data directly into the namespace
`; window.EVOLV.nodes.${nodeName}.config = ${configJSON};
} catch (error) {
throw new Error(`Failed to create endpoint for '${nodeName}': ${error.message}`); console.log('${nodeName} config loaded and endpoint created');
} `;
} catch (error) {
throw new Error(`Failed to create endpoint for '${nodeName}': ${error.message}`);
} }
}
} }
module.exports = ConfigManager; module.exports = ConfigManager;

View File

@@ -245,10 +245,6 @@
{ {
"value": "fysicalControl", "value": "fysicalControl",
"description": "Controlled via physical buttons or switches; ignores external automated commands." "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." "description": "The operational mode of the machine."
@@ -260,7 +256,13 @@
"type": "object", "type": "object",
"schema":{ "schema":{
"auto": { "auto": {
"default": ["statusCheck", "execMovement", "execSequence", "emergencyStop"], "default": [
"statuscheck",
"execmovement",
"execsequence",
"emergencystop",
"entermaintenance"
],
"rules": { "rules": {
"type": "set", "type": "set",
"itemType": "string", "itemType": "string",
@@ -268,7 +270,13 @@
} }
}, },
"virtualControl": { "virtualControl": {
"default": ["statusCheck", "execMovement", "execSequence", "emergencyStop"], "default": [
"statuscheck",
"execmovement",
"execsequence",
"emergencystop",
"exitmaintenance"
],
"rules": { "rules": {
"type": "set", "type": "set",
"itemType": "string", "itemType": "string",
@@ -276,25 +284,22 @@
} }
}, },
"fysicalControl": { "fysicalControl": {
"default": ["statusCheck", "emergencyStop"], "default": [
"statuscheck",
"emergencystop",
"entermaintenance",
"exitmaintenance"
],
"rules": { "rules": {
"type": "set", "type": "set",
"itemType": "string", "itemType": "string",
"description": "Actions allowed in fysicalControl mode." "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." "description": "Information about valid command sources recognized by the machine."
} },
},
"allowedSources":{ "allowedSources":{
"default": {}, "default": {},
"rules": { "rules": {
@@ -386,6 +391,22 @@
"itemType": "string", "itemType": "string",
"description": "Sequence of states for booting up the machine." "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"
}
} }
} }
}, },
@@ -412,14 +433,6 @@
], ],
"description": "The frequency at which calculations are performed." "description": "The frequency at which calculations are performed."
} }
},
"flowNumber": {
"default": 1,
"rules": {
"type": "number",
"nullable": false,
"description": "Defines which effluent flow of the parent node to handle."
}
} }
} }

View File

@@ -11,12 +11,8 @@ class ChildRegistrationUtils {
this.logger.debug(`Registering child: ${name} (${id}) as ${softwareType} at ${positionVsParent}`); this.logger.debug(`Registering child: ${name} (${id}) as ${softwareType} at ${positionVsParent}`);
// Enhanced child setup - multiple parents // Enhanced child setup
if (Array.isArray(child.parent)) { child.parent = this.mainClass;
child.parent.push(this.mainClass);
} else {
child.parent = [this.mainClass];
}
child.positionVsParent = positionVsParent; child.positionVsParent = positionVsParent;
// Enhanced measurement container with rich context // Enhanced measurement container with rich context

View File

@@ -64,7 +64,7 @@ class OutputUtils {
influxDBFormat(changedFields, config , flatTags) { influxDBFormat(changedFields, config , flatTags) {
// Create the measurement and topic using softwareType and name config.functionality.softwareType + . // 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 = { const payload = {
measurement: measurement, measurement: measurement,
fields: changedFields, fields: changedFields,
@@ -104,24 +104,23 @@ class OutputUtils {
return { return {
// general properties // general properties
id: config.general?.id, id: config.general?.id,
name: config.general?.name,
unit: config.general?.unit,
// functionality properties // functionality properties
softwareType: config.functionality?.softwareType, softwareType: config.functionality?.softwareType,
role: config.functionality?.role, role: config.functionality?.role,
// asset properties (exclude machineCurve) // asset properties (exclude machineCurve)
uuid: config.asset?.uuid, uuid: config.asset?.uuid,
tagcode: config.asset?.tagcode,
geoLocation: config.asset?.geoLocation, geoLocation: config.asset?.geoLocation,
supplier: config.asset?.supplier, category: config.asset?.category,
type: config.asset?.type, type: config.asset?.type,
subType: config.asset?.subType,
model: config.asset?.model, model: config.asset?.model,
unit: config.general?.unit,
}; };
} }
processFormat(changedFields,config) { processFormat(changedFields,config) {
// Create the measurement and topic using softwareType and name config.functionality.softwareType + . // 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 payload = changedFields;
const topic = measurement; const topic = measurement;
const msg = { topic: topic, payload: payload }; const msg = { topic: topic, payload: payload };

View File

@@ -52,7 +52,11 @@ class state{
return this.stateManager.getRunTimeHours(); return this.stateManager.getRunTimeHours();
} }
getMaintenanceTimeHours(){
return this.stateManager.getMaintenanceTimeHours();
}
async moveTo(targetPosition) { async moveTo(targetPosition) {
// Check for invalid conditions and throw errors // Check for invalid conditions and throw errors

View File

@@ -205,6 +205,10 @@
{ {
"value": "off", "value": "off",
"description": "Machine is off." "description": "Machine is off."
},
{
"value": "maintenance",
"description": "Machine locked for inspection or repair; automatic control disabled."
} }
], ],
"description": "Current state of the machine." "description": "Current state of the machine."
@@ -216,7 +220,7 @@
"type": "object", "type": "object",
"schema": { "schema": {
"idle": { "idle": {
"default": ["starting", "off","emergencystop"], "default": ["starting", "off","emergencystop","maintenance"],
"rules":{ "rules":{
"type": "set", "type": "set",
"itemType": "string", "itemType": "string",
@@ -280,7 +284,7 @@
} }
}, },
"off": { "off": {
"default": ["idle","emergencystop"], "default": ["idle","emergencystop","maintenance"],
"rules":{ "rules":{
"type": "set", "type": "set",
"itemType": "string", "itemType": "string",
@@ -288,12 +292,20 @@
} }
}, },
"emergencystop": { "emergencystop": {
"default": ["idle","off"], "default": ["idle","off","maintenance"],
"rules":{ "rules":{
"type": "set", "type": "set",
"itemType": "string", "itemType": "string",
"description": "Allowed transitions from emergency stop state." "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." "description": "Allowed transitions between states."

View File

@@ -48,10 +48,14 @@ class stateManager {
// Define valid transitions (can be extended dynamically if needed) // Define valid transitions (can be extended dynamically if needed)
this.validTransitions = config.state.allowedTransitions; this.validTransitions = config.state.allowedTransitions;
// NEW: Initialize runtime tracking //runtime tracking
this.runTimeHours = 0; // cumulative runtime in hours this.runTimeHours = 0; // cumulative runtime in hours
this.runTimeStart = null; // timestamp when active state began 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) // Define active states (runtime counts only in these states)
this.activeStates = config.state.activeStates; this.activeStates = config.state.activeStates;
} }
@@ -59,7 +63,7 @@ class stateManager {
getCurrentState() { getCurrentState() {
return this.currentState; return this.currentState;
} }
transitionTo(newState,signal) { transitionTo(newState,signal) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (signal && signal.aborted) { if (signal && signal.aborted) {
@@ -73,8 +77,9 @@ class stateManager {
); //go back early and reject promise ); //go back early and reject promise
} }
// NEW: Handle runtime tracking based on active states //Time tracking based on active states
this.handleRuntimeTracking(newState); this.handleRuntimeTracking(newState);
this.handleMaintenancetimeTracking(newState);
const transitionDuration = this.transitionTimes[this.currentState] || 0; // Default to 0 if no transition time const transitionDuration = this.transitionTimes[this.currentState] || 0; // Default to 0 if no transition time
this.logger.debug( this.logger.debug(
@@ -100,7 +105,7 @@ class stateManager {
} }
handleRuntimeTracking(newState) { 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 wasActive = this.activeStates.has(this.currentState);
const willBeActive = this.activeStates.has(newState); const willBeActive = this.activeStates.has(newState);
if (wasActive && !willBeActive && this.runTimeStart) { 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) { isValidTransition(newState) {
this.logger.debug( this.logger.debug(
`Check 1 Transition valid ? From ${ `Check 1 Transition valid ? From ${
@@ -150,7 +177,6 @@ class stateManager {
return this.descriptions[state] || "No description available."; return this.descriptions[state] || "No description available.";
} }
// NEW: Getter to retrieve current cumulative runtime (active time) in hours.
getRunTimeHours() { getRunTimeHours() {
// If currently active add the ongoing duration. // If currently active add the ongoing duration.
let currentElapsed = 0; let currentElapsed = 0;
@@ -159,6 +185,15 @@ class stateManager {
} }
return this.runTimeHours + currentElapsed; 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; module.exports = stateManager;