From 10a161783323419ef55e36e6a54dc3aef928409a Mon Sep 17 00:00:00 2001 From: Miguel Date: Sat, 6 Sep 2025 19:49:03 +0200 Subject: [PATCH] feat: Implement Debug Console Server for enhanced logging and remote monitoring --- .../HydraulicSimulationManager.cs | 11 +- HydraulicSimulator/simHydraulicBase.cs | 6 +- MainViewModel.cs | 69 ++++- ObjetosSim/HydraulicComponents/osHydTank.cs | 16 +- Services/DebugConsoleServer.cs | 281 ++++++++++++++++++ 5 files changed, 366 insertions(+), 17 deletions(-) create mode 100644 Services/DebugConsoleServer.cs diff --git a/HydraulicSimulator/HydraulicSimulationManager.cs b/HydraulicSimulator/HydraulicSimulationManager.cs index 33996a0..4816a85 100644 --- a/HydraulicSimulator/HydraulicSimulationManager.cs +++ b/HydraulicSimulator/HydraulicSimulationManager.cs @@ -534,7 +534,7 @@ namespace CtrEditor.HydraulicSimulator /* if (VerboseOutput) { - Debug.WriteLine($"Bomba {pump.GetType().Name}: Velocidad={pump.SpeedRatio:F2}, " + + Trace.WriteLine($"Bomba {pump.GetType().Name}: Velocidad={pump.SpeedRatio:F2}, " + $"Funcionando={pump.IsRunning}, Dirección={pump.PumpDirection}"); } */ @@ -567,17 +567,18 @@ namespace CtrEditor.HydraulicSimulator // Calcular presión basada en nivel si no es presión fija if (!tank.IsFixedPressure) { - // P = ρgh + P_atmosferica + // P = ρgh + P_atmosferica (resultado en Pa) double pressureFromLevel = SimulationFluid.Rho * 9.80665 * tank.Level; // + presión atmosférica - tank.TankPressure = pressureFromLevel; + // Convertir de Pa a bar (1 bar = 100000 Pa) + tank.TankPressure = pressureFromLevel / 100000.0; } // Debug output deshabilitado para mejorar rendimiento /* if (VerboseOutput) { - Debug.WriteLine($"Tanque {tank.GetType().Name}: Nivel={tank.Level:F2}m, " + - $"Presión={tank.TankPressure:F0}Pa, PresionFija={tank.IsFixedPressure}"); + Trace.WriteLine($"Tanque {tank.GetType().Name}: Nivel={tank.Level:F2}m, " + + $"Presión={tank.TankPressure:F3}bar ({tank.TankPressure * 100000.0:F0}Pa), PresionFija={tank.IsFixedPressure}"); } */ } diff --git a/HydraulicSimulator/simHydraulicBase.cs b/HydraulicSimulator/simHydraulicBase.cs index c4ee0c1..ee2351c 100644 --- a/HydraulicSimulator/simHydraulicBase.cs +++ b/HydraulicSimulator/simHydraulicBase.cs @@ -111,9 +111,9 @@ namespace CtrEditor.HydraulicSimulator if (WpfObject is osHydTank tank) { TankPressure = tank.TankPressure; - CurrentLevel = tank.CurrentLevel; - MaxLevel = tank.MaxLevel; - MinLevel = tank.MinLevel; + CurrentLevel = tank.CurrentLevelM; + MaxLevel = tank.MaxLevelM; + MinLevel = tank.MinLevelM; CrossSectionalArea = tank.CrossSectionalArea; IsFixedPressure = tank.IsFixedPressure; } diff --git a/MainViewModel.cs b/MainViewModel.cs index 036d0ad..9c69161 100644 --- a/MainViewModel.cs +++ b/MainViewModel.cs @@ -23,6 +23,7 @@ using System.Timers; // Para el nuevo timer de simulación más preciso using CtrEditor.PopUps; using System.Windows.Data; using CommunityToolkit.Mvvm.Input; +using CtrEditor.Services; // Para MCPServer y DebugConsoleServer using System.Text.RegularExpressions; using System.Collections.Specialized; using CtrEditor.Serialization; // Add this line @@ -75,6 +76,9 @@ namespace CtrEditor // Servidor MCP para control remoto private MCPServer _mcpServer; + // Servidor de debug console para logging + private DebugConsoleServer _debugConsoleServer; + [ObservableProperty] private int mcpServerPort = 5006; @@ -1787,6 +1791,9 @@ namespace CtrEditor await _mcpServer.StartAsync(); Debug.WriteLine($"[MCP] Servidor MCP iniciado en puerto {McpServerPort}"); + + // Iniciar también el servidor de debug console + StartDebugConsoleServer(); } catch (Exception ex) { @@ -1803,8 +1810,10 @@ namespace CtrEditor try { _mcpServer?.Stop(); - Debug.WriteLine("[MCP] Servidor MCP detenido"); + + // Detener también el servidor de debug console + StopDebugConsoleServer(); } catch (Exception ex) { @@ -1821,6 +1830,9 @@ namespace CtrEditor { _mcpServer?.Dispose(); _mcpServer = null; + + // Limpiar también el servidor de debug console + CleanupDebugConsoleServer(); } catch (Exception ex) { @@ -1828,6 +1840,61 @@ namespace CtrEditor } } + /// + /// Inicia el servidor de debug console + /// + private void StartDebugConsoleServer() + { + try + { + if (_debugConsoleServer != null) + { + _debugConsoleServer.Dispose(); + } + + _debugConsoleServer = new DebugConsoleServer(5007); // Puerto fijo para debug + _debugConsoleServer.Start(); + + Debug.WriteLine("[Debug Console] Servidor de debug console iniciado en puerto 5007"); + } + catch (Exception ex) + { + Debug.WriteLine($"[Debug Console] Error iniciando servidor de debug console: {ex.Message}"); + } + } + + /// + /// Detiene el servidor de debug console + /// + private void StopDebugConsoleServer() + { + try + { + _debugConsoleServer?.Stop(); + Debug.WriteLine("[Debug Console] Servidor de debug console detenido"); + } + catch (Exception ex) + { + Debug.WriteLine($"[Debug Console] Error deteniendo servidor de debug console: {ex.Message}"); + } + } + + /// + /// Limpia el servidor de debug console al cerrar la aplicación + /// + private void CleanupDebugConsoleServer() + { + try + { + _debugConsoleServer?.Dispose(); + _debugConsoleServer = null; + } + catch (Exception ex) + { + Debug.WriteLine($"[Debug Console] Error limpiando servidor de debug console: {ex.Message}"); + } + } + #endregion } diff --git a/ObjetosSim/HydraulicComponents/osHydTank.cs b/ObjetosSim/HydraulicComponents/osHydTank.cs index 1fa392f..91fe4f2 100644 --- a/ObjetosSim/HydraulicComponents/osHydTank.cs +++ b/ObjetosSim/HydraulicComponents/osHydTank.cs @@ -634,10 +634,10 @@ namespace CtrEditor.ObjetosSim // El tanque siempre es un nodo de presión fija si está configurado así if (IsFixedPressure) { - // Convertir bar a Pa para el sistema hidráulico interno - var pressurePa = TankPressure * 100000.0; + // TankPressure ya está en Pa, no necesita conversión + var pressurePa = TankPressure; nodes.Add(new HydraulicNodeDefinition(Nombre, true, pressurePa, GetTankDescription())); - //Debug.WriteLine($"Tanque {Nombre}: Nodo de presión fija creado - {TankPressure:F2} bar ({pressurePa:F0} Pa)"); + //Debug.WriteLine($"Tanque {Nombre}: Nodo de presión fija creado - {TankPressure:F0} Pa ({TankPressure/100000.0:F2} bar)"); } else { @@ -655,10 +655,10 @@ namespace CtrEditor.ObjetosSim // Obtener flujos conectados UpdateFlowsFromConnectedPipes(flows); - // Actualizar presión (convertir de Pa a bar) + // Actualizar presión (convertir de Pa a bar para consistencia) if (pressures.ContainsKey(Nombre)) { - CurrentPressure = pressures[Nombre] / 100000.0; // Pa a bar + CurrentPressure = pressures[Nombre] / 100000.0; // Convertir Pa a bar } // Actualizar nivel basado en balance de flujo @@ -666,9 +666,9 @@ namespace CtrEditor.ObjetosSim if (VerboseLogging()) { - Debug.WriteLine($"Tanque {Nombre}: Nivel={CurrentLevelM:F2}m ({FillPercentage:F1}%), " + + Trace.WriteLine($"Tanque {Nombre}: Nivel={CurrentLevelM:F2}m ({FillPercentage:F1}%), " + $"Flujo_entrada={InletFlow:F2}L/min, Flujo_salida={OutletFlow:F2}L/min, " + - $"Balance={FlowBalance:F2}L/min, Presión={CurrentPressure:F2}bar, Fluido={CurrentFluidDescription}"); + $"Balance={FlowBalance:F2}L/min, Presión={CurrentPressure:F3}bar ({CurrentPressure * 100000.0:F0}Pa), Fluido={CurrentFluidDescription}"); } } @@ -1013,7 +1013,7 @@ namespace CtrEditor.ObjetosSim // Debug para verificar cambios if (VerboseLogging() && Math.Abs(volumeChangeL) > 0.1) { - Debug.WriteLine($"Tanque {Nombre}: Δt={deltaTime:F3}min, ΔVolumen={volumeChangeL:F2}L, " + + Trace.WriteLine($"Tanque {Nombre}: Δt={deltaTime:F3}min, ΔVolumen={volumeChangeL:F2}L, " + $"Volumen={CurrentVolumeL:F1}L, Estado={MixingState}, Fluido={CurrentFluidDescription}"); } } diff --git a/Services/DebugConsoleServer.cs b/Services/DebugConsoleServer.cs new file mode 100644 index 0000000..b9d0fb5 --- /dev/null +++ b/Services/DebugConsoleServer.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CtrEditor.Services +{ + /// + /// Servidor TCP que captura y retransmite mensajes de debug console + /// Permite al MCP Proxy escuchar los logs de debug de CtrEditor + /// + public class DebugConsoleServer : IDisposable + { + private readonly int _port; + private TcpListener _tcpListener; + private bool _isRunning; + private CancellationTokenSource _cancellationTokenSource; + private readonly object _lockObject = new object(); + private readonly ConcurrentQueue _messageQueue = new ConcurrentQueue(); + private readonly ConcurrentBag _connectedClients = new ConcurrentBag(); + + // Custom TraceListener para capturar Debug.WriteLine + private DebugTraceListener _traceListener; + + public DebugConsoleServer(int port = 5007) + { + _port = port; + _cancellationTokenSource = new CancellationTokenSource(); + } + + /// + /// Inicia el servidor de debug console + /// + public void Start() + { + if (_isRunning) return; + + try + { + _tcpListener = new TcpListener(IPAddress.Loopback, _port); + _tcpListener.Start(); + _isRunning = true; + + // Instalar el trace listener personalizado solo para Trace + _traceListener = new DebugTraceListener(this); + Trace.Listeners.Add(_traceListener); + + Trace.WriteLine($"[Debug Console Server] Servidor de debug iniciado en puerto {_port}"); + + // Procesar conexiones en background + _ = Task.Run(async () => await AcceptConnectionsAsync(_cancellationTokenSource.Token)); + + // Procesar cola de mensajes + _ = Task.Run(async () => await ProcessMessageQueueAsync(_cancellationTokenSource.Token)); + } + catch (Exception ex) + { + Debug.WriteLine($"[Debug Console Server] Error al iniciar servidor: {ex.Message}"); + throw; + } + } + + /// + /// Detiene el servidor de debug console + /// + public void Stop() + { + if (!_isRunning) return; + + lock (_lockObject) + { + if (!_isRunning) return; + + _isRunning = false; + _cancellationTokenSource?.Cancel(); + + // Remover el trace listener + if (_traceListener != null) + { + Trace.Listeners.Remove(_traceListener); + _traceListener = null; + } + + _tcpListener?.Stop(); + + // Cerrar todas las conexiones de clientes + foreach (var client in _connectedClients) + { + try + { + client?.Close(); + } + catch { } + } + + Debug.WriteLine("[Debug Console Server] Servidor de debug detenido"); + } + } + + /// + /// Acepta conexiones de clientes + /// + private async Task AcceptConnectionsAsync(CancellationToken cancellationToken) + { + try + { + while (_isRunning && !cancellationToken.IsCancellationRequested) + { + Debug.WriteLine("[Debug Console Server] Esperando conexión de cliente..."); + var tcpClient = await _tcpListener.AcceptTcpClientAsync(); + _connectedClients.Add(tcpClient); + + Debug.WriteLine("[Debug Console Server] Cliente debug conectado"); + + // Manejar cliente en background + _ = Task.Run(async () => await HandleClientAsync(tcpClient, cancellationToken)); + } + } + catch (Exception ex) + { + if (_isRunning) + { + Debug.WriteLine($"[Debug Console Server] Error aceptando conexión: {ex.Message}"); + } + } + } + + /// + /// Maneja un cliente conectado + /// + private async Task HandleClientAsync(TcpClient client, CancellationToken cancellationToken) + { + try + { + var stream = client.GetStream(); + + // Enviar mensaje de bienvenida + var welcomeMessage = $"[Debug Console Server] Conectado al servidor de debug de CtrEditor en puerto {_port}\n"; + await SendMessageToClientAsync(stream, welcomeMessage); + + // Mantener conexión viva hasta cancelación + while (_isRunning && client.Connected && !cancellationToken.IsCancellationRequested) + { + await Task.Delay(1000, cancellationToken); + } + } + catch (Exception ex) + { + Debug.WriteLine($"[Debug Console Server] Error en cliente debug: {ex.Message}"); + } + finally + { + try + { + client?.Close(); + } + catch { } + Debug.WriteLine("[Debug Console Server] Cliente debug desconectado"); + } + } + + /// + /// Procesa la cola de mensajes y los envía a todos los clientes conectados + /// + private async Task ProcessMessageQueueAsync(CancellationToken cancellationToken) + { + try + { + while (_isRunning && !cancellationToken.IsCancellationRequested) + { + if (_messageQueue.TryDequeue(out string message)) + { + await BroadcastMessageAsync(message); + } + else + { + await Task.Delay(50, cancellationToken); // Pequeña pausa si no hay mensajes + } + } + } + catch (Exception ex) + { + if (_isRunning) + { + Debug.WriteLine($"[Debug Console Server] Error procesando cola de mensajes: {ex.Message}"); + } + } + } + + /// + /// Envía un mensaje a todos los clientes conectados + /// + private async Task BroadcastMessageAsync(string message) + { + var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"); + var formattedMessage = $"[{timestamp}] {message}\n"; + + var clientsToRemove = new List(); + + foreach (var client in _connectedClients) + { + try + { + if (client.Connected) + { + var stream = client.GetStream(); + await SendMessageToClientAsync(stream, formattedMessage); + } + else + { + clientsToRemove.Add(client); + } + } + catch + { + clientsToRemove.Add(client); + } + } + + // Remover clientes desconectados + // Nota: ConcurrentBag no permite eliminación directa, pero no es crítico + // Los clientes desconectados se manejan en HandleClientAsync + } + + /// + /// Envía un mensaje a un cliente específico + /// + private async Task SendMessageToClientAsync(NetworkStream stream, string message) + { + var data = Encoding.UTF8.GetBytes(message); + await stream.WriteAsync(data, 0, data.Length); + await stream.FlushAsync(); + } + + /// + /// Agrega un mensaje a la cola para transmisión + /// + public void QueueMessage(string message) + { + if (_isRunning) + { + _messageQueue.Enqueue(message); + } + } + + public void Dispose() + { + Stop(); + _cancellationTokenSource?.Dispose(); + } + } + + /// + /// TraceListener personalizado que captura mensajes de Debug.WriteLine + /// y los envía al servidor de debug console + /// + internal class DebugTraceListener : TraceListener + { + private readonly DebugConsoleServer _server; + + public DebugTraceListener(DebugConsoleServer server) + { + _server = server; + } + + public override void Write(string message) + { + _server?.QueueMessage(message); + } + + public override void WriteLine(string message) + { + _server?.QueueMessage(message); + } + } +}