426 lines
15 KiB
Python
426 lines
15 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Sistema de Pruebas Hidráulicas con Timing Preciso
|
|
================================================
|
|
|
|
Utiliza el tiempo real de simulación reportado por CtrEditor
|
|
para mediciones precisas e independientes del sistema.
|
|
|
|
Autor: Sistema de Automatización CtrEditor
|
|
Fecha: Enero 2025
|
|
"""
|
|
|
|
import json
|
|
import socket
|
|
import time
|
|
from dataclasses import dataclass, asdict
|
|
from typing import List, Dict, Any, Optional
|
|
|
|
|
|
@dataclass
|
|
class TankInfo:
|
|
name: str
|
|
level_m: float
|
|
volume_l: float
|
|
primary_fluid: str
|
|
secondary_fluid: str
|
|
primary_volume: float
|
|
secondary_volume: float
|
|
mixing_volume: float
|
|
|
|
|
|
@dataclass
|
|
class PumpInfo:
|
|
name: str
|
|
is_running: bool
|
|
current_flow: float
|
|
max_flow: float
|
|
pump_head: float
|
|
|
|
|
|
@dataclass
|
|
class PipeInfo:
|
|
name: str
|
|
current_flow: float
|
|
pressure_drop: float
|
|
|
|
|
|
class HydraulicTestSystemV2:
|
|
def __init__(self, host="localhost", port=5005):
|
|
self.host = host
|
|
self.port = port
|
|
self.test_results = []
|
|
|
|
def send_mcp_request(self, method: str, params: Dict = None) -> Dict:
|
|
"""Envía una solicitud MCP al servidor proxy"""
|
|
if params is None:
|
|
params = {"random_string": "test"}
|
|
|
|
request = {
|
|
"jsonrpc": "2.0",
|
|
"id": 1,
|
|
"method": "tools/call",
|
|
"params": {"name": method, "arguments": params},
|
|
}
|
|
|
|
try:
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
sock.settimeout(10)
|
|
sock.connect((self.host, self.port))
|
|
|
|
request_str = json.dumps(request) + "\n"
|
|
sock.sendall(request_str.encode())
|
|
|
|
response = sock.recv(4096).decode()
|
|
return json.loads(response)
|
|
|
|
except Exception as e:
|
|
print(f"❌ Error en comunicación MCP: {e}")
|
|
return {"error": str(e)}
|
|
|
|
def get_simulation_timing(self) -> Dict[str, Any]:
|
|
"""Obtiene información detallada del timing de simulación"""
|
|
response = self.send_mcp_request("get_simulation_status")
|
|
if "result" in response:
|
|
result = response["result"]
|
|
return {
|
|
"is_running": result.get("is_running", False),
|
|
"elapsed_ms": result.get("simulation_elapsed_ms", 0),
|
|
"elapsed_seconds": result.get("simulation_elapsed_seconds", 0.0),
|
|
}
|
|
return {"is_running": False, "elapsed_ms": 0, "elapsed_seconds": 0.0}
|
|
|
|
def wait_simulation_time(
|
|
self, target_seconds: float, max_real_time: float = None
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Espera hasta que la simulación haya ejecutado un tiempo determinado
|
|
|
|
Args:
|
|
target_seconds: Tiempo objetivo de simulación en segundos
|
|
max_real_time: Tiempo máximo real a esperar (default: target_seconds * 5)
|
|
|
|
Returns:
|
|
Dict con información del timing final
|
|
"""
|
|
if max_real_time is None:
|
|
max_real_time = target_seconds * 5 # 5x timeout por defecto
|
|
|
|
print(f"⏰ Esperando {target_seconds}s de simulación real...")
|
|
|
|
target_ms = target_seconds * 1000
|
|
start_real_time = time.time()
|
|
last_progress_time = start_real_time
|
|
last_simulation_ms = 0
|
|
|
|
while True:
|
|
current_real_time = time.time()
|
|
|
|
# Timeout si excede tiempo máximo real
|
|
if current_real_time - start_real_time > max_real_time:
|
|
print(f"⚠️ Timeout después de {max_real_time:.1f}s reales")
|
|
break
|
|
|
|
# Obtener estado actual
|
|
timing = self.get_simulation_timing()
|
|
current_simulation_ms = timing["elapsed_ms"]
|
|
|
|
# Verificar si la simulación sigue corriendo
|
|
if not timing["is_running"]:
|
|
print(f"⚠️ Simulación detenida en {current_simulation_ms}ms")
|
|
break
|
|
|
|
# Verificar progreso (evitar simulaciones colgadas)
|
|
if current_simulation_ms > last_simulation_ms:
|
|
last_progress_time = current_real_time
|
|
last_simulation_ms = current_simulation_ms
|
|
elif current_real_time - last_progress_time > 5.0: # 5s sin progreso
|
|
print(f"⚠️ Sin progreso en simulación por 5 segundos")
|
|
break
|
|
|
|
# Verificar si alcanzamos el objetivo
|
|
if current_simulation_ms >= target_ms:
|
|
print(
|
|
f"✅ Objetivo alcanzado: {current_simulation_ms}ms ({timing['elapsed_seconds']:.3f}s)"
|
|
)
|
|
break
|
|
|
|
# Mostrar progreso cada segundo
|
|
if (
|
|
int(current_real_time) > int(start_real_time)
|
|
and int(current_real_time) % 2 == 0
|
|
):
|
|
progress = (current_simulation_ms / target_ms) * 100
|
|
print(
|
|
f"📊 Progreso: {progress:.1f}% ({current_simulation_ms}ms de {target_ms}ms)"
|
|
)
|
|
|
|
time.sleep(0.1) # Check cada 100ms
|
|
|
|
return timing
|
|
|
|
def get_tanks(self) -> List[TankInfo]:
|
|
"""Obtiene información de todos los tanques"""
|
|
response = self.send_mcp_request("list_objects")
|
|
tanks = []
|
|
|
|
if "result" in response and "objects" in response["result"]:
|
|
for obj in response["result"]["objects"]:
|
|
if obj["type"] == "osHydTank":
|
|
props = obj["properties"]
|
|
tank = TankInfo(
|
|
name=props.get("name", "Unknown"),
|
|
level_m=props.get("CurrentLevelM", 0.0),
|
|
volume_l=props.get("TotalVolumeL", 0.0),
|
|
primary_fluid=props.get("PrimaryFluidType", "Unknown"),
|
|
secondary_fluid=props.get("SecondaryFluidType", "Unknown"),
|
|
primary_volume=props.get("PrimaryVolumeL", 0.0),
|
|
secondary_volume=props.get("SecondaryVolumeL", 0.0),
|
|
mixing_volume=props.get("MixingVolumeL", 0.0),
|
|
)
|
|
tanks.append(tank)
|
|
|
|
return tanks
|
|
|
|
def get_pumps(self) -> List[PumpInfo]:
|
|
"""Obtiene información de todas las bombas"""
|
|
response = self.send_mcp_request("list_objects")
|
|
pumps = []
|
|
|
|
if "result" in response and "objects" in response["result"]:
|
|
for obj in response["result"]["objects"]:
|
|
if obj["type"] == "osHydPump":
|
|
props = obj["properties"]
|
|
pump = PumpInfo(
|
|
name=props.get("name", "Unknown"),
|
|
is_running=props.get("IsRunning", False),
|
|
current_flow=props.get("CurrentFlow", 0.0),
|
|
max_flow=props.get("MaxFlow", 0.0),
|
|
pump_head=props.get("PumpHead", 0.0),
|
|
)
|
|
pumps.append(pump)
|
|
|
|
return pumps
|
|
|
|
def get_pipes(self) -> List[PipeInfo]:
|
|
"""Obtiene información de todas las tuberías"""
|
|
response = self.send_mcp_request("list_objects")
|
|
pipes = []
|
|
|
|
if "result" in response and "objects" in response["result"]:
|
|
for obj in response["result"]["objects"]:
|
|
if obj["type"] == "osHydPipe":
|
|
props = obj["properties"]
|
|
pipe = PipeInfo(
|
|
name=props.get("name", "Unknown"),
|
|
current_flow=props.get("CurrentFlow", 0.0),
|
|
pressure_drop=props.get("PressureDrop", 0.0),
|
|
)
|
|
pipes.append(pipe)
|
|
|
|
return pipes
|
|
|
|
def start_simulation(self) -> bool:
|
|
"""Inicia la simulación"""
|
|
response = self.send_mcp_request("start_simulation")
|
|
return "result" in response and response["result"].get("success", False)
|
|
|
|
def stop_simulation(self) -> Dict[str, Any]:
|
|
"""Detiene la simulación y retorna timing final"""
|
|
response = self.send_mcp_request("stop_simulation")
|
|
if "result" in response and response["result"].get("success", False):
|
|
return {
|
|
"success": True,
|
|
"elapsed_ms": response["result"].get("simulation_elapsed_ms", 0),
|
|
"elapsed_seconds": response["result"].get(
|
|
"simulation_elapsed_seconds", 0.0
|
|
),
|
|
}
|
|
return {"success": False, "elapsed_ms": 0, "elapsed_seconds": 0.0}
|
|
|
|
def test_precise_flow_equilibrium(
|
|
self, target_simulation_seconds: float = 30.0
|
|
) -> Dict:
|
|
"""
|
|
Prueba de equilibrio de flujo con timing preciso
|
|
|
|
Args:
|
|
target_simulation_seconds: Tiempo objetivo de simulación en segundos
|
|
|
|
Returns:
|
|
Dict con resultados detallados de la prueba
|
|
"""
|
|
print(f"\n🧪 === PRUEBA DE EQUILIBRIO DE FLUJO (TIMING PRECISO) ===")
|
|
print(f"⏱️ Tiempo objetivo: {target_simulation_seconds} segundos de simulación")
|
|
|
|
test_result = {
|
|
"test_name": "Precise Flow Equilibrium",
|
|
"success": False,
|
|
"target_simulation_seconds": target_simulation_seconds,
|
|
"actual_simulation_data": {},
|
|
"measurements": {},
|
|
"details": {},
|
|
}
|
|
|
|
try:
|
|
# Estado inicial
|
|
initial_timing = self.get_simulation_timing()
|
|
initial_tanks = self.get_tanks()
|
|
initial_pumps = self.get_pumps()
|
|
initial_pipes = self.get_pipes()
|
|
|
|
if len(initial_tanks) < 2:
|
|
test_result["details"][
|
|
"error"
|
|
] = "Se requieren al menos 2 tanques para la prueba"
|
|
return test_result
|
|
|
|
print(f"📊 Estado inicial:")
|
|
print(f"⏱️ Tiempo simulación: {initial_timing['elapsed_seconds']:.3f}s")
|
|
for tank in initial_tanks:
|
|
print(f" - {tank.name}: {tank.level_m:.2f}m ({tank.volume_l:.1f}L)")
|
|
|
|
# Iniciar simulación
|
|
if not self.start_simulation():
|
|
test_result["details"]["error"] = "No se pudo iniciar la simulación"
|
|
return test_result
|
|
|
|
print("🚀 Simulación iniciada...")
|
|
|
|
# Esperar tiempo de simulación preciso
|
|
final_timing = self.wait_simulation_time(target_simulation_seconds)
|
|
|
|
# Detener simulación y obtener timing final
|
|
stop_result = self.stop_simulation()
|
|
|
|
# Estado final
|
|
final_tanks = self.get_tanks()
|
|
final_pumps = self.get_pumps()
|
|
final_pipes = self.get_pipes()
|
|
|
|
print(f"📊 Estado final:")
|
|
print(f"⏱️ Tiempo total simulación: {stop_result['elapsed_seconds']:.3f}s")
|
|
for tank in final_tanks:
|
|
print(f" - {tank.name}: {tank.level_m:.2f}m ({tank.volume_l:.1f}L)")
|
|
|
|
# Calcular balance de masa
|
|
initial_total_volume = sum(tank.volume_l for tank in initial_tanks)
|
|
final_total_volume = sum(tank.volume_l for tank in final_tanks)
|
|
mass_balance = {
|
|
"initial_volume_l": initial_total_volume,
|
|
"final_volume_l": final_total_volume,
|
|
"difference_l": final_total_volume - initial_total_volume,
|
|
"conservation_percentage": (
|
|
(final_total_volume / initial_total_volume * 100)
|
|
if initial_total_volume > 0
|
|
else 0
|
|
),
|
|
}
|
|
|
|
print(f"⚖️ Balance de masa:")
|
|
print(f" - Volumen inicial: {mass_balance['initial_volume_l']:.2f}L")
|
|
print(f" - Volumen final: {mass_balance['final_volume_l']:.2f}L")
|
|
print(f" - Diferencia: {mass_balance['difference_l']:.3f}L")
|
|
print(f" - Conservación: {mass_balance['conservation_percentage']:.2f}%")
|
|
|
|
# Almacenar mediciones con timing preciso
|
|
test_result["actual_simulation_data"] = {
|
|
"initial_timing": initial_timing,
|
|
"final_timing": final_timing,
|
|
"stop_timing": stop_result,
|
|
"actual_simulation_seconds": stop_result["elapsed_seconds"],
|
|
"timing_accuracy": abs(
|
|
target_simulation_seconds - stop_result["elapsed_seconds"]
|
|
),
|
|
}
|
|
|
|
test_result["measurements"] = {
|
|
"target_simulation_seconds": target_simulation_seconds,
|
|
"actual_simulation_seconds": stop_result["elapsed_seconds"],
|
|
"initial_tanks": [asdict(tank) for tank in initial_tanks],
|
|
"final_tanks": [asdict(tank) for tank in final_tanks],
|
|
"pumps": [asdict(pump) for pump in final_pumps],
|
|
"pipes": [asdict(pipe) for pipe in final_pipes],
|
|
"mass_balance": mass_balance,
|
|
}
|
|
|
|
# Verificar éxito (conservación de masa dentro del 1%)
|
|
conservation_ok = (
|
|
abs(mass_balance["difference_l"])
|
|
< 0.01 * mass_balance["initial_volume_l"]
|
|
)
|
|
timing_ok = (
|
|
abs(target_simulation_seconds - stop_result["elapsed_seconds"]) < 1.0
|
|
) # Tolerancia de 1s
|
|
|
|
test_result["success"] = conservation_ok and timing_ok
|
|
|
|
if conservation_ok:
|
|
print("✅ Conservación de masa: EXITOSA")
|
|
else:
|
|
print("❌ Conservación de masa: FALLÓ")
|
|
|
|
if timing_ok:
|
|
print("✅ Precisión de timing: EXITOSA")
|
|
else:
|
|
print("❌ Precisión de timing: FALLÓ")
|
|
|
|
except Exception as e:
|
|
test_result["details"]["error"] = str(e)
|
|
print(f"❌ Error durante la prueba: {e}")
|
|
|
|
return test_result
|
|
|
|
def run_all_tests(self) -> None:
|
|
"""Ejecuta todas las pruebas disponibles"""
|
|
print("🚀 === INICIO DE PRUEBAS HIDRÁULICAS CON TIMING PRECISO ===")
|
|
|
|
# Prueba 1: Equilibrio de flujo preciso
|
|
test1 = self.test_precise_flow_equilibrium(
|
|
15.0
|
|
) # 15 segundos para prueba rápida
|
|
self.test_results.append(test1)
|
|
|
|
# Guardar resultados
|
|
timestamp = time.strftime("%Y%m%d_%H%M%S")
|
|
filename = f"Scripts/HydraulicTestResults_TimingV2_{timestamp}.json"
|
|
|
|
results_data = {
|
|
"timestamp": timestamp,
|
|
"test_summary": {
|
|
"total_tests": len(self.test_results),
|
|
"successful_tests": sum(
|
|
1 for test in self.test_results if test["success"]
|
|
),
|
|
"failed_tests": sum(
|
|
1 for test in self.test_results if not test["success"]
|
|
),
|
|
},
|
|
"tests": self.test_results,
|
|
}
|
|
|
|
with open(filename, "w", encoding="utf-8") as f:
|
|
json.dump(results_data, f, indent=2, ensure_ascii=False)
|
|
|
|
print(f"\n📄 Resultados guardados en: {filename}")
|
|
|
|
# Resumen final
|
|
successful = results_data["test_summary"]["successful_tests"]
|
|
total = results_data["test_summary"]["total_tests"]
|
|
print(f"\n📊 === RESUMEN FINAL ===")
|
|
print(f"✅ Pruebas exitosas: {successful}/{total}")
|
|
|
|
if successful == total:
|
|
print(
|
|
"🎉 ¡TODAS LAS PRUEBAS PASARON! Sistema hidráulico validado con timing preciso."
|
|
)
|
|
else:
|
|
print("⚠️ Algunas pruebas fallaron. Revisar resultados detallados.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Ejecutar las pruebas
|
|
test_system = HydraulicTestSystemV2()
|
|
test_system.run_all_tests()
|
|
|