#!/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()