using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text; using CycleGUI; using StandardScene.Utils; namespace StandardScene.Charge { /// /// 通讯监控面板(CycleGUI 版,替代原 WinForms 窗体)。 /// /// 单实例:再次打开则把已有面板置前。 /// 订阅 ,批量刷新 UI(500ms 节流),最多显示 100 行。 /// 支持 IP 筛选、暂停/继续、清空(二次确认)、选中报文解析详情。 /// /// 保留可实例化 + 以兼容既有调用。 /// public class CommunicationMonitorForm { private const int MaxDisplayRows = 100; private const int UiBatchSize = 20; private const int StatsRefreshMs = 500; private const string TableId = "comm-monitor-msgs"; private readonly Color SendRowColor = Color.FromArgb(232, 245, 233); private readonly Color ReceiveRowColor = Color.FromArgb(227, 242, 253); private readonly Color SelectedRowColor = Color.FromArgb(255, 249, 196); private readonly CommunicationMessageService MessageService = CommunicationMessageService.Instance; private Panel _panel; private bool _subscribed; private bool _paused; private int _selectedIpIndex; private string[] _ipOptions = { "全部" }; private int _selectedRowIndex = -1; private string _parsedText = ""; private string _statsText = ""; private List _displayMessages = new List(); private readonly Queue PendingMessages = new Queue(); private readonly object PendingLock = new object(); private DateTime _lastStatsRefresh = DateTime.MinValue; private bool _pendingStatsRefresh; /// 打开(或置前)通讯监控面板。兼容原 new CommunicationMonitorForm().Show() 调用方式。 public void Show() => Open(); /// 打开(或置前)通讯监控面板。 public static void Open() => (_instance ??= new CommunicationMonitorForm()).OpenCore(); private static CommunicationMonitorForm _instance; private void OpenCore() { if (_panel != null) { try { _panel.BringToFront(); return; } catch { _panel = null; } } _paused = false; _selectedRowIndex = -1; _parsedText = ""; RefreshIpFilter(); ReloadFromService(); var panel = GUI.DeclarePanel() .ShowTitle("通讯监控") .SetDefaultDocking(Panel.Docking.None) .InitSize(1400, 800) .InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f); _panel = panel; panel.IfTerminalQuit(() => { Unsubscribe(); _panel = null; }); Subscribe(); panel.Define(pb => { if (pb.Closing()) { Unsubscribe(); panel.Exit(); _panel = null; return; } FlushPendingBatch(); if (pb.DropdownBox("IP筛选", _ipOptions, ref _selectedIpIndex)) ReloadFromService(); pb.SameLine(16); if (pb.Button(_paused ? "继续" : "暂停", distinct: "comm-pause")) _paused = !_paused; pb.SameLine(8); if (pb.Button("刷新", distinct: "comm-refresh")) { RefreshIpFilter(); ReloadFromService(); RequestStatisticsRefresh(); } pb.SameLine(8); if (pb.Button("清空", distinct: "comm-clear")) { CycleUiHelper.ConfirmThen("确定要清空所有报文记录吗?", () => { MessageService.Clear(); lock (PendingLock) PendingMessages.Clear(); RefreshIpFilter(); _displayMessages.Clear(); _selectedRowIndex = -1; _parsedText = ""; RequestStatisticsRefresh(); }); } pb.SameLine(8); if (pb.Button("关闭", distinct: "comm-close")) { Unsubscribe(); panel.Exit(); _panel = null; return; } MaybeRefreshStatistics(); pb.Label(_statsText); pb.Table(TableId, new[] { "时间", "方向", "IP地址", "端口", "长度", "原始数据", "站点", "类型", "操作" }, _displayMessages.Count, (row, i) => { var msg = _displayMessages[i]; row.SetColor(_selectedRowIndex == i ? SelectedRowColor : msg.Direction == MessageDirection.Send ? SendRowColor : ReceiveRowColor); row.Label($"{msg.Timestamp:HH:mm:ss.fff}"); row.Label(msg.Direction == MessageDirection.Send ? "发送" : "接收"); row.Label(msg.IpAddress ?? ""); row.Label($"{msg.Port}"); row.Label($"{msg.Length}"); row.Label(TruncateRawData(msg.RawData)); row.Label(string.IsNullOrEmpty(msg.StationId) ? "-" : msg.StationId); row.Label(msg.Type ?? ""); if (row.ButtonGroup(new[] { "解析" }, new[] { "解析该报文" }) == 0) { _selectedRowIndex = i; _parsedText = BuildParsedText(msg); } }, height: 20, enableSearch: true); pb.SeparatorText("报文解析"); pb.SelectableText(null, _parsedText ?? "", copyButton: true); pb.Panel.Repaint(repaintTimeMs: 500); }); } private void Subscribe() { if (_subscribed) return; MessageService.MessageAdded += OnMessageAdded; _subscribed = true; } private void Unsubscribe() { if (!_subscribed) return; MessageService.MessageAdded -= OnMessageAdded; _subscribed = false; lock (PendingLock) PendingMessages.Clear(); } private void OnMessageAdded(object sender, CommunicationMessage message) { if (!_subscribed || message == null) return; lock (PendingLock) PendingMessages.Enqueue(message); _panel?.Repaint(); } /// 定时批量刷新 UI,避免每条报文都抢占渲染线程。 private void FlushPendingBatch() { if (_paused) return; List batch = null; lock (PendingLock) { if (PendingMessages.Count == 0) return; int count = Math.Min(UiBatchSize, PendingMessages.Count); batch = new List(count); for (int i = 0; i < count; i++) batch.Add(PendingMessages.Dequeue()); } if (batch == null || batch.Count == 0) return; var filter = SelectedIpFilter(); bool displayChanged = false; foreach (var message in batch) { EnsureIpInFilter(message.IpAddress); if (string.IsNullOrEmpty(filter) || filter == "全部" || filter == message.IpAddress) { InsertMessageAtTop(message); displayChanged = true; } } if (displayChanged) RequestStatisticsRefresh(); } private void InsertMessageAtTop(CommunicationMessage msg) { _displayMessages.Insert(0, msg); while (_displayMessages.Count > MaxDisplayRows) _displayMessages.RemoveAt(_displayMessages.Count - 1); if (_selectedRowIndex >= 0) _selectedRowIndex++; } private void ReloadFromService() { try { var filter = SelectedIpFilter(); var messages = string.IsNullOrEmpty(filter) || filter == "全部" ? MessageService.GetAllMessages() : MessageService.GetMessagesByIp(filter); _displayMessages = messages.Take(MaxDisplayRows).ToList(); _selectedRowIndex = -1; _parsedText = ""; _pendingStatsRefresh = false; UpdateStatistics(_displayMessages.Count); } catch (Exception ex) { _statsText = $"加载报文失败: {ex.Message}"; } } private void RefreshIpFilter() { try { var selectedIp = SelectedIpFilter(); var options = new List { "全部" }; var ipAddresses = MessageService.GetUniqueIpAddresses(); if (ipAddresses != null) { foreach (var ip in ipAddresses) { if (!string.IsNullOrEmpty(ip)) options.Add(ip); } } _ipOptions = options.ToArray(); if (!string.IsNullOrEmpty(selectedIp)) { var idx = Array.IndexOf(_ipOptions, selectedIp); _selectedIpIndex = idx >= 0 ? idx : 0; } else { _selectedIpIndex = 0; } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"刷新IP筛选失败: {ex.Message}"); } } private void EnsureIpInFilter(string ipAddress) { if (string.IsNullOrWhiteSpace(ipAddress)) return; if (_ipOptions.Contains(ipAddress)) return; var list = _ipOptions.ToList(); list.Add(ipAddress); _ipOptions = list.ToArray(); } private string SelectedIpFilter() { if (_ipOptions == null || _ipOptions.Length == 0) return "全部"; if (_selectedIpIndex < 0 || _selectedIpIndex >= _ipOptions.Length) return "全部"; return _ipOptions[_selectedIpIndex]; } private void RequestStatisticsRefresh() { _pendingStatsRefresh = true; } /// 统计信息低频刷新(500ms)。 private void MaybeRefreshStatistics() { if (!_pendingStatsRefresh) return; if (DateTime.Now - _lastStatsRefresh < TimeSpan.FromMilliseconds(StatsRefreshMs)) return; _pendingStatsRefresh = false; _lastStatsRefresh = DateTime.Now; UpdateStatistics(_displayMessages.Count); } private void UpdateStatistics(int displayCount) { try { var allMessages = MessageService.GetAllMessages(); if (allMessages == null) { _statsText = "统计信息加载失败"; return; } var sendCount = allMessages.Count(m => m.Direction == MessageDirection.Send); var receiveCount = allMessages.Count(m => m.Direction == MessageDirection.Receive); _statsText = $"显示: {displayCount} | 总数: {allMessages.Count} | 发送: {sendCount} | 接收: {receiveCount}"; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"更新统计信息失败: {ex.Message}"); _statsText = "统计信息加载失败"; } } private string TruncateRawData(string rawData, int maxLen = 48) { if (string.IsNullOrEmpty(rawData)) return ""; return rawData.Length <= maxLen ? rawData : rawData.Substring(0, maxLen) + "…"; } private string BuildParsedText(CommunicationMessage message) { if (message == null) return ""; try { var parsed = new StringBuilder(); parsed.AppendLine("=== 报文解析 ==="); parsed.AppendLine($"时间: {message.Timestamp:yyyy-MM-dd HH:mm:ss.fff}"); parsed.AppendLine($"方向: {(message.Direction == MessageDirection.Send ? "发送" : "接收")}"); parsed.AppendLine($"地址: {message.IpAddress}:{message.Port}"); parsed.AppendLine($"站点: {message.StationId ?? "未关联"}"); parsed.AppendLine($"长度: {message.Length} 字节"); parsed.AppendLine(); parsed.AppendLine("=== 原始数据 (HEX) ==="); parsed.AppendLine(message.RawData); parsed.AppendLine(); parsed.AppendLine("=== 数据解析 ==="); if (message.Direction == MessageDirection.Send) { var sendData = MessageService.ParseSendRawData(message.RawData, message.Type); parsed.AppendLine("示例解析:"); parsed.AppendLine($"充电指令:{sendData.ChargeCommand}"); parsed.AppendLine($"发送电压:{sendData.SetVoltage}"); parsed.AppendLine($"发送电流:{sendData.SetCurrent}"); parsed.AppendLine($"车辆ID:{sendData.CurrentVehicleId}"); parsed.AppendLine($"车辆电量:{sendData.BatteryLevel}"); parsed.AppendLine($"车辆电压:{sendData.CarVoltage}"); parsed.AppendLine($"车辆电流:{sendData.CarCurrent}"); } else { var recData = MessageService.ParseReceiveRawData(message.RawData, message.Type); string mechanismStatus = (int)recData.MechanismStatus == 1 ? "伸出" : (int)recData.MechanismStatus == 2 ? "缩回" : (int)recData.MechanismStatus == 3 ? "运动中" : recData.MechanismStatus.ToString(); parsed.AppendLine("示例解析:"); parsed.AppendLine($"机构状态:{mechanismStatus}"); parsed.AppendLine($"实时电压:{recData.RealTimeVoltage}"); parsed.AppendLine($"实时电流:{recData.RealTimeCurrent}"); parsed.AppendLine($"充电量: {recData.BatteryAH}"); parsed.AppendLine($"是否报警:{recData.HasAlarm}"); parsed.AppendLine($"充电状态:{recData.Status}"); } return parsed.ToString(); } catch (Exception ex) { return $"解析失败: {ex.Message}"; } } } }