feat: Implement Debug Console Server for enhanced logging and remote monitoring

This commit is contained in:
Miguel 2025-09-06 19:49:03 +02:00
parent 749f0f7eff
commit 10a1617833
5 changed files with 366 additions and 17 deletions

View File

@ -534,7 +534,7 @@ namespace CtrEditor.HydraulicSimulator
/* /*
if (VerboseOutput) 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}"); $"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 // Calcular presión basada en nivel si no es presión fija
if (!tank.IsFixedPressure) 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 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 // Debug output deshabilitado para mejorar rendimiento
/* /*
if (VerboseOutput) if (VerboseOutput)
{ {
Debug.WriteLine($"Tanque {tank.GetType().Name}: Nivel={tank.Level:F2}m, " + Trace.WriteLine($"Tanque {tank.GetType().Name}: Nivel={tank.Level:F2}m, " +
$"Presión={tank.TankPressure:F0}Pa, PresionFija={tank.IsFixedPressure}"); $"Presión={tank.TankPressure:F3}bar ({tank.TankPressure * 100000.0:F0}Pa), PresionFija={tank.IsFixedPressure}");
} }
*/ */
} }

View File

@ -111,9 +111,9 @@ namespace CtrEditor.HydraulicSimulator
if (WpfObject is osHydTank tank) if (WpfObject is osHydTank tank)
{ {
TankPressure = tank.TankPressure; TankPressure = tank.TankPressure;
CurrentLevel = tank.CurrentLevel; CurrentLevel = tank.CurrentLevelM;
MaxLevel = tank.MaxLevel; MaxLevel = tank.MaxLevelM;
MinLevel = tank.MinLevel; MinLevel = tank.MinLevelM;
CrossSectionalArea = tank.CrossSectionalArea; CrossSectionalArea = tank.CrossSectionalArea;
IsFixedPressure = tank.IsFixedPressure; IsFixedPressure = tank.IsFixedPressure;
} }

View File

@ -23,6 +23,7 @@ using System.Timers; // Para el nuevo timer de simulación más preciso
using CtrEditor.PopUps; using CtrEditor.PopUps;
using System.Windows.Data; using System.Windows.Data;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using CtrEditor.Services; // Para MCPServer y DebugConsoleServer
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Collections.Specialized; using System.Collections.Specialized;
using CtrEditor.Serialization; // Add this line using CtrEditor.Serialization; // Add this line
@ -75,6 +76,9 @@ namespace CtrEditor
// Servidor MCP para control remoto // Servidor MCP para control remoto
private MCPServer _mcpServer; private MCPServer _mcpServer;
// Servidor de debug console para logging
private DebugConsoleServer _debugConsoleServer;
[ObservableProperty] [ObservableProperty]
private int mcpServerPort = 5006; private int mcpServerPort = 5006;
@ -1787,6 +1791,9 @@ namespace CtrEditor
await _mcpServer.StartAsync(); await _mcpServer.StartAsync();
Debug.WriteLine($"[MCP] Servidor MCP iniciado en puerto {McpServerPort}"); Debug.WriteLine($"[MCP] Servidor MCP iniciado en puerto {McpServerPort}");
// Iniciar también el servidor de debug console
StartDebugConsoleServer();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -1803,8 +1810,10 @@ namespace CtrEditor
try try
{ {
_mcpServer?.Stop(); _mcpServer?.Stop();
Debug.WriteLine("[MCP] Servidor MCP detenido"); Debug.WriteLine("[MCP] Servidor MCP detenido");
// Detener también el servidor de debug console
StopDebugConsoleServer();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -1821,6 +1830,9 @@ namespace CtrEditor
{ {
_mcpServer?.Dispose(); _mcpServer?.Dispose();
_mcpServer = null; _mcpServer = null;
// Limpiar también el servidor de debug console
CleanupDebugConsoleServer();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -1828,6 +1840,61 @@ namespace CtrEditor
} }
} }
/// <summary>
/// Inicia el servidor de debug console
/// </summary>
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}");
}
}
/// <summary>
/// Detiene el servidor de debug console
/// </summary>
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}");
}
}
/// <summary>
/// Limpia el servidor de debug console al cerrar la aplicación
/// </summary>
private void CleanupDebugConsoleServer()
{
try
{
_debugConsoleServer?.Dispose();
_debugConsoleServer = null;
}
catch (Exception ex)
{
Debug.WriteLine($"[Debug Console] Error limpiando servidor de debug console: {ex.Message}");
}
}
#endregion #endregion
} }

View File

@ -634,10 +634,10 @@ namespace CtrEditor.ObjetosSim
// El tanque siempre es un nodo de presión fija si está configurado así // El tanque siempre es un nodo de presión fija si está configurado así
if (IsFixedPressure) if (IsFixedPressure)
{ {
// Convertir bar a Pa para el sistema hidráulico interno // TankPressure ya está en Pa, no necesita conversión
var pressurePa = TankPressure * 100000.0; var pressurePa = TankPressure;
nodes.Add(new HydraulicNodeDefinition(Nombre, true, pressurePa, GetTankDescription())); 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 else
{ {
@ -655,10 +655,10 @@ namespace CtrEditor.ObjetosSim
// Obtener flujos conectados // Obtener flujos conectados
UpdateFlowsFromConnectedPipes(flows); UpdateFlowsFromConnectedPipes(flows);
// Actualizar presión (convertir de Pa a bar) // Actualizar presión (convertir de Pa a bar para consistencia)
if (pressures.ContainsKey(Nombre)) 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 // Actualizar nivel basado en balance de flujo
@ -666,9 +666,9 @@ namespace CtrEditor.ObjetosSim
if (VerboseLogging()) 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, " + $"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 // Debug para verificar cambios
if (VerboseLogging() && Math.Abs(volumeChangeL) > 0.1) 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}"); $"Volumen={CurrentVolumeL:F1}L, Estado={MixingState}, Fluido={CurrentFluidDescription}");
} }
} }

View File

@ -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
{
/// <summary>
/// Servidor TCP que captura y retransmite mensajes de debug console
/// Permite al MCP Proxy escuchar los logs de debug de CtrEditor
/// </summary>
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<string> _messageQueue = new ConcurrentQueue<string>();
private readonly ConcurrentBag<TcpClient> _connectedClients = new ConcurrentBag<TcpClient>();
// Custom TraceListener para capturar Debug.WriteLine
private DebugTraceListener _traceListener;
public DebugConsoleServer(int port = 5007)
{
_port = port;
_cancellationTokenSource = new CancellationTokenSource();
}
/// <summary>
/// Inicia el servidor de debug console
/// </summary>
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;
}
}
/// <summary>
/// Detiene el servidor de debug console
/// </summary>
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");
}
}
/// <summary>
/// Acepta conexiones de clientes
/// </summary>
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}");
}
}
}
/// <summary>
/// Maneja un cliente conectado
/// </summary>
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");
}
}
/// <summary>
/// Procesa la cola de mensajes y los envía a todos los clientes conectados
/// </summary>
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}");
}
}
}
/// <summary>
/// Envía un mensaje a todos los clientes conectados
/// </summary>
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<TcpClient>();
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
}
/// <summary>
/// Envía un mensaje a un cliente específico
/// </summary>
private async Task SendMessageToClientAsync(NetworkStream stream, string message)
{
var data = Encoding.UTF8.GetBytes(message);
await stream.WriteAsync(data, 0, data.Length);
await stream.FlushAsync();
}
/// <summary>
/// Agrega un mensaje a la cola para transmisión
/// </summary>
public void QueueMessage(string message)
{
if (_isRunning)
{
_messageQueue.Enqueue(message);
}
}
public void Dispose()
{
Stop();
_cancellationTokenSource?.Dispose();
}
}
/// <summary>
/// TraceListener personalizado que captura mensajes de Debug.WriteLine
/// y los envía al servidor de debug console
/// </summary>
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);
}
}
}