447 lines
17 KiB
C#
447 lines
17 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// Simulador TSNet en tiempo cuasi-real
|
|
/// Ejecuta simulaciones de 1 segundo consecutivamente permitiendo cambios entre ciclos
|
|
/// </summary>
|
|
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<string, PumpState> _pumpStates = new();
|
|
private readonly Dictionary<string, ValveState> _valveStates = new();
|
|
private readonly Dictionary<string, TankState> _tankStates = new();
|
|
|
|
#endregion
|
|
|
|
#region Events
|
|
|
|
public event EventHandler<SimulationCycleCompletedEventArgs> CycleCompleted;
|
|
public event EventHandler<SimulationErrorEventArgs> 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
|
|
|
|
/// <summary>
|
|
/// Inicia la simulación en tiempo cuasi-real
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Detiene la simulación en tiempo real
|
|
/// </summary>
|
|
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");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Actualiza la velocidad de una bomba (será aplicado en el próximo ciclo)
|
|
/// </summary>
|
|
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}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Actualiza la apertura de una válvula (será aplicado en el próximo ciclo)
|
|
/// </summary>
|
|
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}%");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Obtiene el estado actual de los tanques
|
|
/// </summary>
|
|
public Dictionary<string, TankState> GetCurrentTankStates()
|
|
{
|
|
lock (_lockObject)
|
|
{
|
|
return new Dictionary<string, TankState>(_tankStates);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Obtiene el estado actual de las bombas
|
|
/// </summary>
|
|
public Dictionary<string, PumpState> GetCurrentPumpStates()
|
|
{
|
|
lock (_lockObject)
|
|
{
|
|
return new Dictionary<string, PumpState>(_pumpStates);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Obtiene el estado actual de las válvulas
|
|
/// </summary>
|
|
public Dictionary<string, ValveState> GetCurrentValveStates()
|
|
{
|
|
lock (_lockObject)
|
|
{
|
|
return new Dictionary<string, ValveState>(_valveStates);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
|
|
/// <summary>
|
|
/// Ejecuta un ciclo de simulación (llamado cada segundo por el timer)
|
|
/// </summary>
|
|
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 });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Aplica los cambios de estado actuales a la red de simulación
|
|
/// </summary>
|
|
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}%");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ejecuta una simulación TSNet de exactamente 1 segundo
|
|
/// </summary>
|
|
private async Task<TSNetResult> 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
|
|
};
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Procesa los resultados de la simulación y actualiza los estados
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Estado de un tanque en tiempo real
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Estado de una bomba en tiempo real
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Estado de una válvula en tiempo real
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Argumentos del evento de ciclo completado
|
|
/// </summary>
|
|
public class SimulationCycleCompletedEventArgs : EventArgs
|
|
{
|
|
public double SimulationTime { get; set; }
|
|
public TimeSpan CycleDuration { get; set; }
|
|
public Dictionary<string, TankState> TankStates { get; set; }
|
|
public Dictionary<string, PumpState> PumpStates { get; set; }
|
|
public Dictionary<string, ValveState> ValveStates { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Argumentos del evento de error de simulación
|
|
/// </summary>
|
|
public class SimulationErrorEventArgs : EventArgs
|
|
{
|
|
public string Message { get; set; }
|
|
}
|
|
|
|
#endregion
|
|
} |