forked from RnD/valveGroupControl
change to new version of code
This commit is contained in:
@@ -59,19 +59,46 @@ class nodeClass {
|
|||||||
|
|
||||||
_updateNodeStatus() {
|
_updateNodeStatus() {
|
||||||
const vg = this.source;
|
const vg = this.source;
|
||||||
const mode = vg.mode;
|
const mode = vg.currentMode;
|
||||||
const scaling = vg.scaling;
|
const scaling = vg.scaling;
|
||||||
const totalFlow =
|
const totalFlow =
|
||||||
Math.round(
|
Math.round(
|
||||||
vg.measurements
|
vg.measurements
|
||||||
.type("flow")
|
.type("flow")
|
||||||
.variant("measured")
|
.variant("predicted")
|
||||||
.position("downstream")
|
.position("atEquipment")
|
||||||
.getCurrentValue() * 1
|
.getCurrentValue() * 1
|
||||||
) / 1;
|
) / 1;
|
||||||
|
const maxDeltaP =
|
||||||
|
Math.round(
|
||||||
|
vg.measurements
|
||||||
|
.type("maxDeltaP")
|
||||||
|
.variant("predicted")
|
||||||
|
.position("delta")
|
||||||
|
.getCurrentValue() * 1
|
||||||
|
) / 1;
|
||||||
|
// Verkrijg alle valves uit de geneste structuur:
|
||||||
|
const allValves = [];
|
||||||
|
|
||||||
|
// Voeg alle valve objects toe aan allValves
|
||||||
|
for (const [softwareType, typeData] of Object.entries(vg.child)) {
|
||||||
|
// Filter alleen valves
|
||||||
|
if (softwareType === 'valve') {
|
||||||
|
// Loop door categories (valves, etc.)
|
||||||
|
for (const childrenArray of Object.values(typeData)) {
|
||||||
|
if (Array.isArray(childrenArray)) {
|
||||||
|
allValves.push(...childrenArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate total capacity based on available valves
|
// Calculate total capacity based on available valves
|
||||||
const availableValves = Object.values(vg.valves).filter((valve) => {
|
const availableValves = allValves.filter((valve) => {
|
||||||
|
if (!valve || !valve.state) {
|
||||||
|
console.log('Valve missing state:', valve);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const state = valve.state.getCurrentState();
|
const state = valve.state.getCurrentState();
|
||||||
const mode = valve.currentMode;
|
const mode = valve.currentMode;
|
||||||
return !(
|
return !(
|
||||||
@@ -81,8 +108,8 @@ class nodeClass {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// const totalCapacity = Math.round(vg.dynamicTotals.flow.max * 1) / 1; ADD LATER?
|
//const totalCapacity = Math.round(vg.dynamicTotals.flow.max * 1) / 1; //ADD LATER?
|
||||||
|
|
||||||
// Determine overall status based on available valves
|
// Determine overall status based on available valves
|
||||||
const status =
|
const status =
|
||||||
availableValves.length > 0
|
availableValves.length > 0
|
||||||
@@ -91,7 +118,7 @@ class nodeClass {
|
|||||||
|
|
||||||
|
|
||||||
// Generate status text in a single line
|
// Generate status text in a single line
|
||||||
const text = ` ${mode} | 💨=${totalFlow} | ${status}`;
|
const text = ` ${mode} | 💨=${totalFlow} | maxDeltaP = ${maxDeltaP} | ${status}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fill: availableValves.length > 0 ? "green" : "red",
|
fill: availableValves.length > 0 ? "green" : "red",
|
||||||
@@ -178,6 +205,7 @@ class nodeClass {
|
|||||||
childObj.source,
|
childObj.source,
|
||||||
msg.positionVsParent
|
msg.positionVsParent
|
||||||
);
|
);
|
||||||
|
//console.log('CHECK HERE NOW', childObj);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'setMode':
|
case 'setMode':
|
||||||
|
|||||||
@@ -63,9 +63,7 @@ class ValveGroupControl {
|
|||||||
// Initialize measurements
|
// Initialize measurements
|
||||||
this.measurements = new MeasurementContainer();
|
this.measurements = new MeasurementContainer();
|
||||||
this.child = {};
|
this.child = {};
|
||||||
this.valves = {}; // hold child object so we can get information from its child valves
|
// this.valves = {}; // this.child; // hold child object so we can get information from its child valves
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Initialize variables
|
// Initialize variables
|
||||||
this.maxDeltaP = 0; // max deltaP is 0 als er geen child valves zijn
|
this.maxDeltaP = 0; // max deltaP is 0 als er geen child valves zijn
|
||||||
@@ -74,9 +72,66 @@ class ValveGroupControl {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registerOnChildEvents() {}
|
registerChild(child, softwareType) { //checken welke child er zijn
|
||||||
|
console.log('=== DEBUGGING this.child ===');
|
||||||
|
console.log('Type of this.child:', typeof this.child);
|
||||||
|
console.log('this.child:', this.child);
|
||||||
|
|
||||||
|
if (Object.keys(this.child).length === 0) {
|
||||||
|
console.log('✅ this.child is empty');
|
||||||
|
} else {
|
||||||
|
console.log('✅ this.child contains:', Object.keys(this.child).length, 'items');
|
||||||
|
Object.keys(this.child).forEach(key => {
|
||||||
|
console.log(` - Key: ${key}, Value type: ${typeof this.child[key]}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
registerChild(child, positionVsParent) {
|
this.addEventListenerForChild(child, softwareType); //subscribe to measurement changes from this specific child
|
||||||
|
}
|
||||||
|
|
||||||
|
//MAAK SPECIFIEKE CHILD EVENT LISTENER - KAN EVT IN REGISTERCHILD FUNCTIE WORDEN BIJGEVOEGD
|
||||||
|
addEventListenerForChild(child, softwareType) {
|
||||||
|
if (child && child.emitter) {
|
||||||
|
child.measurements.emitter.on('change', (data) => {
|
||||||
|
const childName = child.config?.general?.name || `${softwareType}_child`;
|
||||||
|
console.log('🔔 EVENT RECEIVED!', childName, data);
|
||||||
|
this.logger.info(`Received change event from child ${childName}:`, data);
|
||||||
|
this.handleChildChange(child, data);
|
||||||
|
});
|
||||||
|
console.log(`✅ Event listener added for ${softwareType} child`);
|
||||||
|
} else {
|
||||||
|
console.log(`❌ Child or emitter missing:`, {
|
||||||
|
child: !!child,
|
||||||
|
emitter: !!child?.measurements.emitter
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChildChange(child, data) {
|
||||||
|
switch(data.type) {
|
||||||
|
case 'flow':
|
||||||
|
if (child.source === 'mg') {
|
||||||
|
this.logger.info("Total flow change from machineGroupControl detected by valveGroupControl");
|
||||||
|
// als totalflow van mg veranderd, bereken dan de nieuwe valve flows
|
||||||
|
this.calcValveFlows();
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'pressure':
|
||||||
|
if (data.position === 'delta') {
|
||||||
|
this.logger.info("DeltaP change from child valve detected by valveGroupControl");
|
||||||
|
// als deltaP van een van de childs valves verandert, bereken dan de nieuwe maxDeltaP
|
||||||
|
this.calcMaxDeltaP();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Insert actions this.calcMaxDeltaP();
|
||||||
|
this.logger.info("Kijk ik kom bij handle child change aan!")
|
||||||
|
break;
|
||||||
|
case 'position':
|
||||||
|
// Handle position changes
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.logger.debug(`Unhandled change type: ${data.type}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isValidSourceForMode(source, mode) {
|
isValidSourceForMode(source, mode) {
|
||||||
@@ -99,7 +154,7 @@ class ValveGroupControl {
|
|||||||
await this.executeSequence(parameter);
|
await this.executeSequence(parameter);
|
||||||
break;
|
break;
|
||||||
case "totalFlowChange":
|
case "totalFlowChange":
|
||||||
await this.updateFlow(parameter);
|
await this.updateFlow("predicted", parameter, "atEquipment");
|
||||||
break;
|
break;
|
||||||
case "emergencyStop":
|
case "emergencyStop":
|
||||||
this.logger.warn(`Emergency stop activated by '${source}'.`);
|
this.logger.warn(`Emergency stop activated by '${source}'.`);
|
||||||
@@ -175,12 +230,13 @@ updateFlow(variant,value,position) {
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
this.logger.warn(`Unrecognized variant '${variant}' for flow update.`);
|
this.logger.warn(`Unrecognized variant '${variant}' for flow update.`);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateMeasurement(variant, subType, value, position) {
|
updateMeasurement(variant, subType, value, position) {
|
||||||
this.logger.debug(`---------------------- updating ${subType} ------------------ `);
|
this.logger.debug(`---------------------- updating ${subType} ------------------ NOT YET FURTHER DEFINED ------------------`);
|
||||||
switch (subType) {
|
switch (subType) {
|
||||||
case "pressure":
|
case "pressure":
|
||||||
// Update pressure measurement
|
// Update pressure measurement
|
||||||
@@ -199,52 +255,129 @@ updateFlow(variant,value,position) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
calcValveFlows() {
|
calcValveFlows() {
|
||||||
const totalFlow = this.measurements.type("flow").variant("measured").position("atEquipment").getCurrentValue(); // get the total flow from the measurement container
|
const totalFlow = this.measurements.type("flow").variant("predicted").position("atEquipment").getCurrentValue(); // get the total flow from the measurement container
|
||||||
let totalKv = 0;
|
let totalKv = 0;
|
||||||
|
|
||||||
this.logger.debug(`Calculating valve flows... ${totalFlow}`); //Checkpoint
|
this.logger.debug(`Calculating valve flows... ${totalFlow}`); //Checkpoint
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for (const key in this.valves){ //bereken sum kv values om verdeling total flow te maken
|
// for (const key in this.child){ //bereken sum kv values om verdeling total flow te maken
|
||||||
this.logger.info('kv: ' + this.valves[key].kv); //CHECKPOINT
|
// if (this.child[key].state.getCurrentPosition() != null) {
|
||||||
if (this.valves[key].state.getCurrentPosition() != null) {
|
// totalKv += this.child[key].kv;
|
||||||
totalKv += this.valves[key].kv;
|
// this.logger.info('Total Kv = ' + totalKv); //CHECKPOINT
|
||||||
this.logger.info('Total Kv = ' + totalKv); //CHECKPOINT
|
// }
|
||||||
}
|
// if(totalKv === 0) {
|
||||||
if(totalKv === 0) {
|
// this.logger.warn('Total Kv is 0, cannot calculate flow distribution.');
|
||||||
this.logger.warn('Total Kv is 0, cannot calculate flow distribution.');
|
// return; // Avoid division by zero
|
||||||
return; // Avoid division by zero
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Loop door de geneste this.child structuur om total kv te berekenen
|
||||||
|
for (const [softwareType, typeData] of Object.entries(this.child)) {
|
||||||
|
// Filter alleen op valves
|
||||||
|
if (softwareType === 'valve') {
|
||||||
|
// Loop door categories (valves, etc.)
|
||||||
|
for (const [category, childrenArray] of Object.entries(typeData)) {
|
||||||
|
if (Array.isArray(childrenArray)) {
|
||||||
|
// Loop door alle valve objecten in deze array
|
||||||
|
childrenArray.forEach((valve, index) => {
|
||||||
|
if (valve && valve.state && valve.state.getCurrentPosition() != null) {
|
||||||
|
totalKv += valve.kv;
|
||||||
|
this.logger.info(`Adding valve ${index} Kv: ${valve.kv}, Total Kv now: ${totalKv}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in this.valves){
|
if (totalKv === 0) {
|
||||||
const valve = this.valves[key];
|
this.logger.warn('Total Kv is 0, cannot calculate flow distribution.');
|
||||||
this.logger.debug(`Calculating ratio for valve total: ${totalKv} valve.kv: ${valve.kv} ratio : ${valve.kv / totalKv}`); //Checkpoint
|
return; // Avoid division by zero
|
||||||
const ratio = valve.kv / totalKv;
|
}
|
||||||
const flow = ratio * totalFlow; // bereken flow per valve
|
|
||||||
|
|
||||||
//update flow per valve in de object zelf wat daar vervolgens weer de nieuwe deltaP berekent
|
// for (const key in this.child){
|
||||||
valve.updateFlow("predicted", flow, "downstream");
|
// const valve = this.child[key];
|
||||||
this.logger.info(`--> Sending updated flow to valves --> ${flow} `); //Checkpoint
|
// this.logger.debug(`Calculating ratio for valve total: ${totalKv} valve.kv: ${valve.kv} ratio : ${valve.kv / totalKv}`); //Checkpoint
|
||||||
|
// const ratio = valve.kv / totalKv;
|
||||||
|
// const flow = ratio * totalFlow; // bereken flow per valve
|
||||||
|
|
||||||
|
// //update flow per valve in de object zelf wat daar vervolgens weer de nieuwe deltaP berekent
|
||||||
|
// valve.updateFlow("predicted", flow, "downstream");
|
||||||
|
// this.logger.info(`--> Sending updated flow to valves --> ${flow} `); //Checkpoint
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Flow verdelen over alle valves
|
||||||
|
for (const [softwareType, typeData] of Object.entries(this.child)) {
|
||||||
|
if (softwareType === 'valve') {
|
||||||
|
for (const [category, childrenArray] of Object.entries(typeData)) {
|
||||||
|
if (Array.isArray(childrenArray)) {
|
||||||
|
childrenArray.forEach((valve, index) => {
|
||||||
|
if (valve && valve.state && valve.state.getCurrentPosition() != null) {
|
||||||
|
const ratio = valve.kv / totalKv;
|
||||||
|
const flow = ratio * totalFlow;
|
||||||
|
|
||||||
|
valve.updateFlow("predicted", flow, "downstream");
|
||||||
|
this.logger.info(`--> Sending updated flow to valve --> Flow: ${flow}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
calcMaxDeltaP() { // bereken de max deltaP van alle child valves
|
calcMaxDeltaP() { // bereken de max deltaP van alle child valves
|
||||||
let maxDeltaP = 0; //max deltaP is 0 als er geen child valves zijn
|
let maxDeltaP = 0;
|
||||||
this.logger.info('Calculating new max deltaP...');
|
for (const [softwareType, typeData] of Object.entries(this.child)) {
|
||||||
for (const key in this.valves) {
|
// Filter alleen op valves
|
||||||
const valve = this.valves[key]; //haal de child valve object op
|
if (softwareType === 'valve') {
|
||||||
const deltaP = valve.measurements.type("pressure").variant("predicted").position("delta").getCurrentValue(); //get delta P
|
// Loop door categories (valves, etc.)
|
||||||
this.logger.info(`Delta P for valve ${key}: ${deltaP}`);
|
for (const [category, childrenArray] of Object.entries(typeData)) {
|
||||||
if (deltaP > maxDeltaP) { //als de deltaP van de child valve groter is dan de huidige maxDeltaP, dan update deze
|
if (Array.isArray(childrenArray)) {
|
||||||
maxDeltaP = deltaP;
|
// Loop door alle valve objecten in deze array
|
||||||
}
|
childrenArray.forEach((valve, index) => {
|
||||||
|
try{
|
||||||
|
const deltaP = valve.measurements.type("pressure").variant("predicted").position("delta").getCurrentValue();
|
||||||
|
|
||||||
|
if (deltaP > maxDeltaP) {
|
||||||
|
maxDeltaP = deltaP;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Error retrieving deltaP for valve at index ${index}: ${error}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.logger.info('Max Delta P updated to: ' + maxDeltaP);
|
|
||||||
|
|
||||||
this.maxDeltaP = maxDeltaP; //update de max deltaP in de measurement container van de valveGroupControl class
|
this.measurements.type("maxDeltaP").variant("predicted").position("delta").value(maxDeltaP); //update de max deltaP in de measurement container van de valveGroupControl class
|
||||||
|
this.maxDeltaP = maxDeltaP; //update de max deltaP
|
||||||
|
this.logger.info('Max Delta P updated to: ' + maxDeltaP);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// let maxDeltaP = 0; //max deltaP is 0 als er geen child valves zijn
|
||||||
|
// this.logger.info('Calculating new max deltaP...');
|
||||||
|
// for (const key in this.child) {
|
||||||
|
// const valve = this.child[key]; //haal de child valve object op
|
||||||
|
// const deltaP = valve.measurements.type("pressure").variant("predicted").position("delta").getCurrentValue(); //get delta P
|
||||||
|
// this.logger.info(`Delta P for valve ${key}: ${deltaP}`);
|
||||||
|
// if (deltaP > maxDeltaP) { //als de deltaP van de child valve groter is dan de huidige maxDeltaP, dan update deze
|
||||||
|
// maxDeltaP = deltaP;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// this.logger.info('Max Delta P updated to: ' + maxDeltaP);
|
||||||
|
|
||||||
|
// this.measurements.type("maxDeltaP").variant("predicted").position("delta").value(maxDeltaP); //update de max deltaP in de measurement container van de valveGroupControl class
|
||||||
|
// this.maxDeltaP = maxDeltaP; //update de max deltaP
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
getOutput() {
|
getOutput() {
|
||||||
@@ -273,7 +406,6 @@ updateFlow(variant,value,position) {
|
|||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ValveGroupControl;
|
module.exports = ValveGroupControl;
|
||||||
@@ -318,23 +450,23 @@ const stateConfig = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const valve1 = new valve(valveConfig, stateConfig);
|
//const valve1 = new valve(valveConfig, stateConfig);
|
||||||
//const valve2 = new valve(valveConfig, stateConfig);
|
//const valve2 = new valve(valveConfig, stateConfig);
|
||||||
//const valve3 = new valve(valveConfig, stateConfig);
|
//const valve3 = new valve(valveConfig, stateConfig);
|
||||||
|
|
||||||
valve1.kv = 10; // Set Kv value for valve1
|
//valve1.kv = 10; // Set Kv value for valve1
|
||||||
//valve2.kv = 20; // Set Kv value for valve2
|
//valve2.kv = 20; // Set Kv value for valve2
|
||||||
//valve3.kv = 30; // Set Kv value for valve3
|
//valve3.kv = 30; // Set Kv value for valve3
|
||||||
|
|
||||||
valve1.updateMeasurement("measured", "pressure" , 500, "downstream");
|
//valve1.updateMeasurement("measured", "pressure" , 500, "downstream");
|
||||||
//valve2.updateMeasurement("measured" , "pressure" , 500, "downstream");
|
//valve2.updateMeasurement("measured" , "pressure" , 500, "downstream");
|
||||||
//valve3.updateMeasurement("measured" , "pressure" , 500, "downstream");
|
//valve3.updateMeasurement("measured" , "pressure" , 500, "downstream");
|
||||||
|
|
||||||
const vgc = new ValveGroupControl();
|
//const vgc = new ValveGroupControl();
|
||||||
|
|
||||||
vgc.childRegistrationUtils.registerChild(valve1, "atEquipment");
|
//vgc.childRegistrationUtils.registerChild(valve1, "atEquipment");
|
||||||
//vgc.childRegistrationUtils.registerChild(valve2, "atEquipment");
|
//vgc.childRegistrationUtils.registerChild(valve2, "atEquipment");
|
||||||
//vgc.childRegistrationUtils.registerChild(valve3, "atEquipment");
|
//vgc.childRegistrationUtils.registerChild(valve3, "atEquipment");
|
||||||
|
|
||||||
vgc.updateFlow("measured", 1000, "atEquipment"); // Update total flow to 100 m3/h
|
//vgc.updateFlow("measured", 1000, "atEquipment"); // Update total flow to 100 m3/h
|
||||||
|
|
||||||
|
|||||||
23
vgc.html
23
vgc.html
@@ -1,12 +1,15 @@
|
|||||||
<!--
|
<!--
|
||||||
| S88-niveau | Primair (blokkleur) | Tekstkleur |
|
brabantse delta kleuren:
|
||||||
| ---------------------- | ------------------- | ---------- |
|
#eaf4f1
|
||||||
| **Area** | `#0f52a5` | wit |
|
#86bbdd
|
||||||
| **Process Cell** | `#0c99d9` | wit |
|
#bad33b
|
||||||
| **Unit** | `#50a8d9` | zwart |
|
#0c99d9
|
||||||
| **Equipment (Module)** | `#86bbdd` | zwart |
|
#a9daee
|
||||||
| **Control Module** | `#a9daee` | zwart |
|
#0f52a5
|
||||||
|
#50a8d9
|
||||||
|
#cade63
|
||||||
|
#4f8582
|
||||||
|
#c4cce0
|
||||||
-->
|
-->
|
||||||
<script src="/valveGroupControl/menu.js"></script> <!-- Load the menu script for dynamic dropdowns -->
|
<script src="/valveGroupControl/menu.js"></script> <!-- Load the menu script for dynamic dropdowns -->
|
||||||
<script src="/valveGroupControl/configData.js"></script> <!-- Load the config script for node information -->
|
<script src="/valveGroupControl/configData.js"></script> <!-- Load the config script for node information -->
|
||||||
@@ -14,7 +17,7 @@
|
|||||||
<script>
|
<script>
|
||||||
RED.nodes.registerType('valveGroupControl',{
|
RED.nodes.registerType('valveGroupControl',{
|
||||||
category: "EVOLV",
|
category: "EVOLV",
|
||||||
color: "#50a8d9",
|
color: "#eaf4f1",
|
||||||
defaults: {
|
defaults: {
|
||||||
// Define default properties
|
// Define default properties
|
||||||
name: { value: "" },
|
name: { value: "" },
|
||||||
@@ -35,7 +38,7 @@
|
|||||||
outputs:3,
|
outputs:3,
|
||||||
inputLabels: ["Input"],
|
inputLabels: ["Input"],
|
||||||
outputLabels: ["process", "dbase", "parent"],
|
outputLabels: ["process", "dbase", "parent"],
|
||||||
icon: "font-awesome/fa-tasks",
|
icon: "font-awesome/fa-tachometer",
|
||||||
|
|
||||||
label: function () {
|
label: function () {
|
||||||
return this.positionIcon + " " + "valveGroupControl";
|
return this.positionIcon + " " + "valveGroupControl";
|
||||||
|
|||||||
Reference in New Issue
Block a user