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
}