Files
StandardSence/StandardScene.Core/Charge/CommunicationMonitorForm.cs
T

435 lines
16 KiB
C#
Raw Normal View History

2026-06-14 11:19:15 +08:00
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using FairyView;
using StandardScene.Utils;
2026-06-14 11:19:15 +08:00
namespace StandardScene.Charge
{
/// <summary>
/// 通讯监控面板(CycleGUI 版,替代原 WinForms 窗体)。
/// <list type="bullet">
/// <item>单实例:再次打开则把已有面板置前。</item>
/// <item>订阅 <see cref="CommunicationMessageService.MessageAdded"/>,批量刷新 UI500ms 节流),最多显示 100 行。</item>
/// <item>支持 IP 筛选、暂停/继续、清空(二次确认)、选中报文解析详情。</item>
/// </list>
/// 保留可实例化 + <see cref="Show"/> 以兼容既有调用。
2026-06-14 11:19:15 +08:00
/// </summary>
public class CommunicationMonitorForm
2026-06-14 11:19:15 +08:00
{
private const int MaxDisplayRows = 100;
private const int UiBatchSize = 20;
private const int StatsRefreshMs = 500;
private const string TableId = "comm-monitor-msgs";
2026-06-14 11:19:15 +08:00
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);
2026-06-14 11:19:15 +08:00
private readonly CommunicationMessageService MessageService = CommunicationMessageService.Instance;
2026-06-14 11:19:15 +08:00
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 = "";
2026-06-14 11:19:15 +08:00
private List<CommunicationMessage> _displayMessages = new List<CommunicationMessage>();
private readonly Queue<CommunicationMessage> PendingMessages = new Queue<CommunicationMessage>();
private readonly object PendingLock = new object();
2026-06-14 11:19:15 +08:00
private DateTime _lastStatsRefresh = DateTime.MinValue;
private bool _pendingStatsRefresh;
/// <summary>打开(或置前)通讯监控面板。兼容原 <c>new CommunicationMonitorForm().Show()</c> 调用方式。</summary>
public void Show() => Open();
2026-06-14 11:19:15 +08:00
/// <summary>打开(或置前)通讯监控面板。</summary>
public static void Open() => (_instance ??= new CommunicationMonitorForm()).OpenCore();
private static CommunicationMonitorForm _instance;
private void OpenCore()
2026-06-14 11:19:15 +08:00
{
if (_panel != null)
2026-06-14 11:19:15 +08:00
{
try
{
_panel.BringToFront();
2026-06-14 11:19:15 +08:00
return;
}
catch
{
_panel = null;
}
}
_paused = false;
_selectedRowIndex = -1;
_parsedText = "";
RefreshIpFilter();
ReloadFromService();
2026-06-14 11:19:15 +08:00
var panel = GUI.DeclarePanel()
.ShowTitle("通讯监控")
.ForceFloat()
.InitSize(1400, 800)
.InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f);
_panel = panel;
panel.IfTerminalQuit(() =>
{
Unsubscribe();
_panel = null;
});
2026-06-14 11:19:15 +08:00
Subscribe();
2026-06-14 11:19:15 +08:00
panel.Define(pb =>
{
if (pb.Closing())
2026-06-14 11:19:15 +08:00
{
Unsubscribe();
panel.Exit();
_panel = null;
return;
2026-06-14 11:19:15 +08:00
}
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"))
2026-06-14 11:19:15 +08:00
{
RefreshIpFilter();
ReloadFromService();
RequestStatisticsRefresh();
2026-06-14 11:19:15 +08:00
}
pb.SameLine(8);
if (pb.Button("清空", distinct: "comm-clear"))
2026-06-14 11:19:15 +08:00
{
FairyUiHelper.ConfirmThen("确定要清空所有报文记录吗?", () =>
{
MessageService.Clear();
lock (PendingLock)
PendingMessages.Clear();
RefreshIpFilter();
_displayMessages.Clear();
_selectedRowIndex = -1;
_parsedText = "";
RequestStatisticsRefresh();
});
2026-06-14 11:19:15 +08:00
}
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: FairyUiHelper.TableHeightPx(20), enableSearch: true);
pb.SeparatorText("报文解析");
pb.SelectableText(null, _parsedText ?? "", copyButton: true);
pb.Panel.Repaint(repaintTimeMs: 500);
});
2026-06-14 11:19:15 +08:00
}
private void Subscribe()
2026-06-14 11:19:15 +08:00
{
if (_subscribed)
return;
MessageService.MessageAdded += OnMessageAdded;
_subscribed = true;
}
2026-06-14 11:19:15 +08:00
private void Unsubscribe()
{
if (!_subscribed)
return;
MessageService.MessageAdded -= OnMessageAdded;
_subscribed = false;
lock (PendingLock)
PendingMessages.Clear();
}
2026-06-14 11:19:15 +08:00
private void OnMessageAdded(object sender, CommunicationMessage message)
{
if (!_subscribed || message == null)
return;
2026-06-14 11:19:15 +08:00
lock (PendingLock)
PendingMessages.Enqueue(message);
2026-06-14 11:19:15 +08:00
_panel?.Repaint();
2026-06-14 11:19:15 +08:00
}
/// <summary>定时批量刷新 UI,避免每条报文都抢占渲染线程。</summary>
private void FlushPendingBatch()
2026-06-14 11:19:15 +08:00
{
if (_paused)
2026-06-14 11:19:15 +08:00
return;
List<CommunicationMessage> batch = null;
lock (PendingLock)
2026-06-14 11:19:15 +08:00
{
if (PendingMessages.Count == 0)
2026-06-14 11:19:15 +08:00
return;
int count = Math.Min(UiBatchSize, PendingMessages.Count);
2026-06-14 11:19:15 +08:00
batch = new List<CommunicationMessage>(count);
for (int i = 0; i < count; i++)
batch.Add(PendingMessages.Dequeue());
2026-06-14 11:19:15 +08:00
}
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()
{
2026-06-14 11:19:15 +08:00
try
{
var selectedIp = SelectedIpFilter();
var options = new List<string> { "全部" };
2026-06-14 11:19:15 +08:00
var ipAddresses = MessageService.GetUniqueIpAddresses();
if (ipAddresses != null)
2026-06-14 11:19:15 +08:00
{
foreach (var ip in ipAddresses)
2026-06-14 11:19:15 +08:00
{
if (!string.IsNullOrEmpty(ip))
options.Add(ip);
2026-06-14 11:19:15 +08:00
}
}
_ipOptions = options.ToArray();
if (!string.IsNullOrEmpty(selectedIp))
{
var idx = Array.IndexOf(_ipOptions, selectedIp);
_selectedIpIndex = idx >= 0 ? idx : 0;
}
else
2026-06-14 11:19:15 +08:00
{
_selectedIpIndex = 0;
2026-06-14 11:19:15 +08:00
}
}
catch (Exception ex)
2026-06-14 11:19:15 +08:00
{
System.Diagnostics.Debug.WriteLine($"刷新IP筛选失败: {ex.Message}");
2026-06-14 11:19:15 +08:00
}
}
private void EnsureIpInFilter(string ipAddress)
2026-06-14 11:19:15 +08:00
{
if (string.IsNullOrWhiteSpace(ipAddress))
return;
if (_ipOptions.Contains(ipAddress))
2026-06-14 11:19:15 +08:00
return;
var list = _ipOptions.ToList();
list.Add(ipAddress);
_ipOptions = list.ToArray();
2026-06-14 11:19:15 +08:00
}
private string SelectedIpFilter()
2026-06-14 11:19:15 +08:00
{
if (_ipOptions == null || _ipOptions.Length == 0)
return "全部";
if (_selectedIpIndex < 0 || _selectedIpIndex >= _ipOptions.Length)
return "全部";
return _ipOptions[_selectedIpIndex];
2026-06-14 11:19:15 +08:00
}
private void RequestStatisticsRefresh()
2026-06-14 11:19:15 +08:00
{
_pendingStatsRefresh = true;
2026-06-14 11:19:15 +08:00
}
/// <summary>统计信息低频刷新(500ms)。</summary>
private void MaybeRefreshStatistics()
2026-06-14 11:19:15 +08:00
{
if (!_pendingStatsRefresh)
return;
if (DateTime.Now - _lastStatsRefresh < TimeSpan.FromMilliseconds(StatsRefreshMs))
2026-06-14 11:19:15 +08:00
return;
_pendingStatsRefresh = false;
_lastStatsRefresh = DateTime.Now;
UpdateStatistics(_displayMessages.Count);
2026-06-14 11:19:15 +08:00
}
private void UpdateStatistics(int displayCount)
2026-06-14 11:19:15 +08:00
{
try
{
var allMessages = MessageService.GetAllMessages();
2026-06-14 11:19:15 +08:00
if (allMessages == null)
{
_statsText = "统计信息加载失败";
2026-06-14 11:19:15 +08:00
return;
}
2026-06-14 11:19:15 +08:00
var sendCount = allMessages.Count(m => m.Direction == MessageDirection.Send);
var receiveCount = allMessages.Count(m => m.Direction == MessageDirection.Receive);
_statsText = $"显示: {displayCount} | 总数: {allMessages.Count} | 发送: {sendCount} | 接收: {receiveCount}";
2026-06-14 11:19:15 +08:00
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"更新统计信息失败: {ex.Message}");
_statsText = "统计信息加载失败";
2026-06-14 11:19:15 +08:00
}
}
private string TruncateRawData(string rawData, int maxLen = 48)
2026-06-14 11:19:15 +08:00
{
if (string.IsNullOrEmpty(rawData))
return "";
return rawData.Length <= maxLen ? rawData : rawData.Substring(0, maxLen) + "…";
2026-06-14 11:19:15 +08:00
}
private string BuildParsedText(CommunicationMessage message)
2026-06-14 11:19:15 +08:00
{
if (message == null)
return "";
2026-06-14 11:19:15 +08:00
try
{
var parsed = new StringBuilder();
2026-06-14 11:19:15 +08:00
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)
2026-06-14 11:19:15 +08:00
{
var sendData = MessageService.ParseSendRawData(message.RawData, message.Type);
2026-06-14 11:19:15 +08:00
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}");
2026-06-14 11:19:15 +08:00
}
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();
2026-06-14 11:19:15 +08:00
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}");
2026-06-14 11:19:15 +08:00
}
return parsed.ToString();
2026-06-14 11:19:15 +08:00
}
catch (Exception ex)
{
return $"解析失败: {ex.Message}";
2026-06-14 11:19:15 +08:00
}
}
}
}