CtrEditor/Scripts/HydraulicSystemTests_Timing...

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()