using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using System.Diagnostics; using System.IO; using CtrEditor.HydraulicSimulator.Python; using CtrEditor.HydraulicSimulator.TSNet.Components; using HydraulicSimulator.Models; using CtrEditor.ObjetosSim; namespace CtrEditor.HydraulicSimulator.TSNet { /// /// Simulador TSNet en tiempo cuasi-real /// Ejecuta simulaciones de 1 segundo consecutivamente permitiendo cambios entre ciclos /// public class TSNetRealTimeSimulator : IDisposable { #region Fields private readonly TSNetSimulationManager _simulationManager; private readonly Timer _simulationTimer; private readonly object _lockObject = new object(); private bool _isRunning = false; private bool _disposed = false; private double _currentSimulationTime = 0.0; private DateTime _lastSimulationStart; // Estados que pueden cambiar entre simulaciones private readonly Dictionary _pumpStates = new(); private readonly Dictionary _valveStates = new(); private readonly Dictionary _tankStates = new(); #endregion #region Events public event EventHandler CycleCompleted; public event EventHandler SimulationError; #endregion #region Properties public bool IsRunning => _isRunning; public double CurrentSimulationTime => _currentSimulationTime; public TimeSpan SimulationInterval { get; set; } = TimeSpan.FromSeconds(1.0); #endregion #region Constructor public TSNetRealTimeSimulator() { _simulationManager = new TSNetSimulationManager(); // Configurar para simulaciones de 1 segundo con timestep más pequeño para estabilidad _simulationManager.Configuration.Duration = 1.0; _simulationManager.Configuration.TimeStep = 0.1; // Timestep más pequeño para evitar problemas numéricos // Timer para ejecutar simulaciones cada segundo _simulationTimer = new Timer(ExecuteSimulationCycle, null, Timeout.Infinite, Timeout.Infinite); Debug.WriteLine("TSNetRealTimeSimulator: Inicializado para simulación cuasi-continua con timestep seguro"); } #endregion #region Public Methods /// /// Inicia la simulación en tiempo cuasi-real /// public void StartRealTimeSimulation() { lock (_lockObject) { if (_isRunning) { Debug.WriteLine("TSNetRealTimeSimulator: Simulación ya está ejecutándose"); return; } _isRunning = true; _currentSimulationTime = 0.0; _lastSimulationStart = DateTime.Now; Debug.WriteLine("TSNetRealTimeSimulator: Iniciando simulación en tiempo real"); // Iniciar el timer para ejecutar cada segundo _simulationTimer.Change(TimeSpan.Zero, SimulationInterval); } } /// /// Detiene la simulación en tiempo real /// public void StopRealTimeSimulation() { lock (_lockObject) { if (!_isRunning) { Debug.WriteLine("TSNetRealTimeSimulator: Simulación no está ejecutándose"); return; } _isRunning = false; _simulationTimer.Change(Timeout.Infinite, Timeout.Infinite); Debug.WriteLine($"TSNetRealTimeSimulator: Simulación detenida en t={_currentSimulationTime:F1}s"); } } /// /// Actualiza la velocidad de una bomba (será aplicado en el próximo ciclo) /// public void UpdatePumpSpeed(string pumpId, double speedRatio) { lock (_lockObject) { if (!_pumpStates.ContainsKey(pumpId)) _pumpStates[pumpId] = new PumpState(); _pumpStates[pumpId].SpeedRatio = Math.Max(0.0, Math.Min(1.0, speedRatio)); _pumpStates[pumpId].IsRunning = speedRatio > 0.0; Debug.WriteLine($"TSNetRealTimeSimulator: Bomba {pumpId} velocidad actualizada a {speedRatio:F2}"); } } /// /// Actualiza la apertura de una válvula (será aplicado en el próximo ciclo) /// public void UpdateValveOpening(string valveId, double openingPercentage) { lock (_lockObject) { if (!_valveStates.ContainsKey(valveId)) _valveStates[valveId] = new ValveState(); _valveStates[valveId].OpeningPercentage = Math.Max(0.0, Math.Min(100.0, openingPercentage)); _valveStates[valveId].IsOpen = openingPercentage > 0.0; Debug.WriteLine($"TSNetRealTimeSimulator: Válvula {valveId} apertura actualizada a {openingPercentage:F1}%"); } } /// /// Obtiene el estado actual de los tanques /// public Dictionary GetCurrentTankStates() { lock (_lockObject) { return new Dictionary(_tankStates); } } /// /// Obtiene el estado actual de las bombas /// public Dictionary GetCurrentPumpStates() { lock (_lockObject) { return new Dictionary(_pumpStates); } } /// /// Obtiene el estado actual de las válvulas /// public Dictionary GetCurrentValveStates() { lock (_lockObject) { return new Dictionary(_valveStates); } } #endregion #region Private Methods /// /// Ejecuta un ciclo de simulación (llamado cada segundo por el timer) /// private async void ExecuteSimulationCycle(object state) { if (!_isRunning) return; var cycleStart = DateTime.Now; try { Debug.WriteLine($"TSNetRealTimeSimulator: Iniciando ciclo t={_currentSimulationTime:F1}s"); // 1. Aplicar cambios de estado a la red ApplyStateChangesToNetwork(); // 2. Ejecutar simulación TSNet de 1 segundo var result = await ExecuteSingleSecondSimulation(); // 3. Procesar resultados y actualizar estados if (result.Success) { ProcessSimulationResults(result); _currentSimulationTime += 1.0; // 4. Notificar ciclo completado var cycleArgs = new SimulationCycleCompletedEventArgs { SimulationTime = _currentSimulationTime, CycleDuration = DateTime.Now - cycleStart, TankStates = GetCurrentTankStates(), PumpStates = GetCurrentPumpStates(), ValveStates = GetCurrentValveStates() }; CycleCompleted?.Invoke(this, cycleArgs); Debug.WriteLine($"TSNetRealTimeSimulator: Ciclo completado t={_currentSimulationTime:F1}s en {(DateTime.Now - cycleStart).TotalMilliseconds:F0}ms"); } else { Debug.WriteLine($"TSNetRealTimeSimulator: Error en simulación: {result.Message}"); SimulationError?.Invoke(this, new SimulationErrorEventArgs { Message = result.Message }); } } catch (Exception ex) { Debug.WriteLine($"TSNetRealTimeSimulator: Excepción en ciclo: {ex.Message}"); SimulationError?.Invoke(this, new SimulationErrorEventArgs { Message = ex.Message }); } } /// /// Aplica los cambios de estado actuales a la red de simulación /// private void ApplyStateChangesToNetwork() { // Aplicar estados de bombas foreach (var kvp in _pumpStates) { var pumpId = kvp.Key; var state = kvp.Value; // Encontrar el adaptador de bomba usando método público var adapter = _simulationManager.GetPumpAdapter(pumpId); if (adapter != null) { adapter.Configuration.SpeedRatio = state.SpeedRatio; adapter.Configuration.IsRunning = state.IsRunning; } } // Aplicar estados de válvulas foreach (var kvp in _valveStates) { var valveId = kvp.Key; var state = kvp.Value; // Aplicar cambios a válvulas (implementar cuando tengamos ValveAdapter) Debug.WriteLine($"TSNetRealTimeSimulator: Aplicando válvula {valveId} apertura {state.OpeningPercentage:F1}%"); } } /// /// Ejecuta una simulación TSNet de exactamente 1 segundo /// private async Task ExecuteSingleSecondSimulation() { try { // Configurar para 1 segundo con timestep más pequeño para evitar problemas numéricos _simulationManager.Configuration.Duration = 1.0; _simulationManager.Configuration.TimeStep = 0.1; // Timestep más pequeño para estabilidad numérica // Validar configuración antes de ejecutar if (_simulationManager.Configuration.Duration <= 0) { throw new InvalidOperationException("Duration debe ser mayor que 0"); } if (_simulationManager.Configuration.TimeStep <= 0 || _simulationManager.Configuration.TimeStep > _simulationManager.Configuration.Duration) { throw new InvalidOperationException("TimeStep debe ser mayor que 0 y menor o igual que Duration"); } // Ejecutar simulación var result = await _simulationManager.RunSimulationAsync(); return result; } catch (Exception ex) { Debug.WriteLine($"TSNetRealTimeSimulator: Error ejecutando simulación: {ex.Message}"); return new TSNetResult { Success = false, Message = ex.Message }; } } /// /// Procesa los resultados de la simulación y actualiza los estados /// private void ProcessSimulationResults(TSNetResult result) { // Actualizar estados de tanques con resultados de TSNet var tankCount = 0; foreach (var hydraulicObject in _simulationManager.HydraulicObjects) { if (hydraulicObject is osHydTank tank) { var tankAdapter = _simulationManager.GetTankAdapter(tank.Id.ToString()); if (tankAdapter != null) { var tankId = tankAdapter.TankId; if (!_tankStates.ContainsKey(tankId)) _tankStates[tankId] = new TankState(); // Actualizar con resultados reales de TSNet _tankStates[tankId].CurrentLevel = tankAdapter.Results?.CalculatedLevelM ?? _tankStates[tankId].CurrentLevel; _tankStates[tankId].CurrentVolume = tankAdapter.Results?.CalculatedVolumeL ?? _tankStates[tankId].CurrentVolume; _tankStates[tankId].CurrentPressure = tankAdapter.Results?.CalculatedPressureBar ?? _tankStates[tankId].CurrentPressure; _tankStates[tankId].FlowBalance = tankAdapter.Results?.NetFlowM3s ?? _tankStates[tankId].FlowBalance; tankCount++; } } } // Actualizar estados de bombas con resultados var pumpCount = 0; foreach (var hydraulicObject in _simulationManager.HydraulicObjects) { if (hydraulicObject is osHydPump pump) { var pumpAdapter = _simulationManager.GetPumpAdapter(pump.Id.ToString()); if (pumpAdapter != null) { var pumpId = pumpAdapter.NodeId; if (!_pumpStates.ContainsKey(pumpId)) _pumpStates[pumpId] = new PumpState(); // Actualizar con resultados reales de TSNet _pumpStates[pumpId].CurrentFlow = pumpAdapter.Results?.CalculatedFlowM3s ?? _pumpStates[pumpId].CurrentFlow; _pumpStates[pumpId].CurrentHead = pumpAdapter.Results?.CalculatedHeadM ?? _pumpStates[pumpId].CurrentHead; _pumpStates[pumpId].CurrentPower = pumpAdapter.Results?.PowerConsumptionKW ?? _pumpStates[pumpId].CurrentPower; pumpCount++; } } } Debug.WriteLine($"TSNetRealTimeSimulator: Estados actualizados - Tanques: {tankCount}, Bombas: {pumpCount}"); } #endregion #region IDisposable public void Dispose() { if (!_disposed) { StopRealTimeSimulation(); _simulationTimer?.Dispose(); _simulationManager?.Dispose(); _disposed = true; } } #endregion } #region State Classes /// /// Estado de un tanque en tiempo real /// public class TankState { public double CurrentLevel { get; set; } public double CurrentVolume { get; set; } public double CurrentPressure { get; set; } public double FlowBalance { get; set; } public DateTime LastUpdated { get; set; } = DateTime.Now; } /// /// Estado de una bomba en tiempo real /// public class PumpState { public bool IsRunning { get; set; } = true; public double SpeedRatio { get; set; } = 1.0; public double CurrentFlow { get; set; } public double CurrentHead { get; set; } public double CurrentPower { get; set; } public DateTime LastUpdated { get; set; } = DateTime.Now; } /// /// Estado de una válvula en tiempo real /// public class ValveState { public bool IsOpen { get; set; } = true; public double OpeningPercentage { get; set; } = 100.0; public double CurrentFlow { get; set; } public double PressureDrop { get; set; } public DateTime LastUpdated { get; set; } = DateTime.Now; } #endregion #region Event Args /// /// Argumentos del evento de ciclo completado /// public class SimulationCycleCompletedEventArgs : EventArgs { public double SimulationTime { get; set; } public TimeSpan CycleDuration { get; set; } public Dictionary TankStates { get; set; } public Dictionary PumpStates { get; set; } public Dictionary ValveStates { get; set; } } /// /// Argumentos del evento de error de simulación /// public class SimulationErrorEventArgs : EventArgs { public string Message { get; set; } } #endregion }