562 lines
20 KiB
C#
562 lines
20 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Drawing;
|
||
using System.Linq;
|
||
using System.Windows.Forms;
|
||
|
||
namespace StandardScene.Charge
|
||
{
|
||
/// <summary>
|
||
/// 通讯监控窗体
|
||
/// </summary>
|
||
public partial class CommunicationMonitorForm : Form
|
||
{
|
||
private readonly CommunicationMessageService messageService;
|
||
private bool isFormLoaded = false;
|
||
private bool isFormMessageStop = false;
|
||
private const int MaxDisplayRows = 100;
|
||
private const int UiBatchSize = 20;
|
||
private const int StatsRefreshMs = 500;
|
||
private readonly Queue<CommunicationMessage> pendingMessages = new Queue<CommunicationMessage>();
|
||
private readonly object pendingMessagesLock = new object();
|
||
private readonly Timer uiFlushTimer;
|
||
private readonly Timer statsRefreshTimer;
|
||
private bool pendingStatsRefresh = false;
|
||
private int lastDisplayCountForStats = 0;
|
||
public CommunicationMonitorForm()
|
||
{
|
||
InitializeComponent();
|
||
messageService = CommunicationMessageService.Instance;
|
||
uiFlushTimer = new Timer { Interval = 500 };
|
||
uiFlushTimer.Tick += UiFlushTimer_Tick;
|
||
statsRefreshTimer = new Timer { Interval = StatsRefreshMs };
|
||
statsRefreshTimer.Tick += StatsRefreshTimer_Tick;
|
||
|
||
// 订阅窗体关闭事件
|
||
this.FormClosing += CommunicationMonitorForm_FormClosing;
|
||
}
|
||
|
||
private void CommunicationMonitorForm_Load(object sender, EventArgs e)
|
||
{
|
||
try
|
||
{
|
||
InitializeForm();
|
||
LoadMessages();
|
||
|
||
// 标记窗体已加载完成
|
||
isFormLoaded = true;
|
||
uiFlushTimer.Start();
|
||
statsRefreshTimer.Start();
|
||
|
||
// 在窗体加载完成后再订阅报文添加事件(避免在初始化期间触发)
|
||
messageService.MessageAdded += OnMessageAdded;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show($"窗体加载失败: {ex.Message}\r\n{ex.StackTrace}", "错误",
|
||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
}
|
||
}
|
||
|
||
private void InitializeForm()
|
||
{
|
||
this.Text = "通讯监控";
|
||
this.Size = new Size(1400, 800);
|
||
this.StartPosition = FormStartPosition.CenterScreen;
|
||
this.MinimumSize = new Size(1200, 600);
|
||
|
||
// 初始化IP筛选下拉框
|
||
RefreshIpFilter();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 刷新IP筛选下拉框
|
||
/// </summary>
|
||
private void RefreshIpFilter()
|
||
{
|
||
try
|
||
{
|
||
if (cmbIpFilter == null || messageService == null)
|
||
return;
|
||
|
||
var selectedIp = cmbIpFilter.SelectedItem?.ToString();
|
||
|
||
cmbIpFilter.Items.Clear();
|
||
cmbIpFilter.Items.Add("全部");
|
||
|
||
var ipAddresses = messageService.GetUniqueIpAddresses();
|
||
if (ipAddresses != null)
|
||
{
|
||
foreach (var ip in ipAddresses)
|
||
{
|
||
if (!string.IsNullOrEmpty(ip))
|
||
{
|
||
cmbIpFilter.Items.Add(ip);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 恢复选中项
|
||
if (!string.IsNullOrEmpty(selectedIp) && cmbIpFilter.Items.Contains(selectedIp))
|
||
{
|
||
cmbIpFilter.SelectedItem = selectedIp;
|
||
}
|
||
else if (cmbIpFilter.Items.Count > 0)
|
||
{
|
||
cmbIpFilter.SelectedIndex = 0;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"刷新IP筛选失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 加载报文列表
|
||
/// </summary>
|
||
private void LoadMessages()
|
||
{
|
||
var layoutSuspended = false;
|
||
try
|
||
{
|
||
if (dgvMessages == null|| isFormMessageStop)
|
||
return;
|
||
|
||
var selectedIp = cmbIpFilter?.SelectedItem?.ToString();
|
||
var messages = string.IsNullOrEmpty(selectedIp) || selectedIp == "全部"
|
||
? messageService.GetAllMessages()
|
||
: messageService.GetMessagesByIp(selectedIp);
|
||
|
||
dgvMessages.SuspendLayout();
|
||
layoutSuspended = true;
|
||
dgvMessages.Rows.Clear();
|
||
|
||
foreach (var msg in messages)
|
||
{
|
||
AddMessageRow(msg, false);
|
||
}
|
||
|
||
UpdateStatistics(messages.Count);
|
||
pendingStatsRefresh = false;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show($"加载报文失败: {ex.Message}", "错误",
|
||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
}
|
||
finally
|
||
{
|
||
if (layoutSuspended && dgvMessages != null)
|
||
{
|
||
dgvMessages.ResumeLayout();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 定时批量刷新UI,避免每条报文都抢占UI线程
|
||
/// </summary>
|
||
private void UiFlushTimer_Tick(object sender, EventArgs e)
|
||
{
|
||
if (!isFormLoaded || isFormMessageStop)
|
||
return;
|
||
|
||
List<CommunicationMessage> batch = null;
|
||
lock (pendingMessagesLock)
|
||
{
|
||
if (pendingMessages.Count == 0)
|
||
return;
|
||
|
||
int count = Math.Min(UiBatchSize, pendingMessages.Count);
|
||
batch = new List<CommunicationMessage>(count);
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
batch.Add(pendingMessages.Dequeue());
|
||
}
|
||
}
|
||
|
||
if (batch == null || batch.Count == 0)
|
||
return;
|
||
|
||
dgvMessages.SuspendLayout();
|
||
try
|
||
{
|
||
var selectedIp = cmbIpFilter?.SelectedItem?.ToString();
|
||
bool displayChanged = false;
|
||
|
||
foreach (var message in batch)
|
||
{
|
||
EnsureIpInFilter(message.IpAddress);
|
||
if (string.IsNullOrEmpty(selectedIp) || selectedIp == "全部" || selectedIp == message.IpAddress)
|
||
{
|
||
AddMessageRow(message, true);
|
||
displayChanged = true;
|
||
}
|
||
}
|
||
|
||
if (displayChanged)
|
||
{
|
||
RequestStatisticsRefresh(dgvMessages.Rows.Count);
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
dgvMessages.ResumeLayout();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 统计信息低频刷新(500ms)
|
||
/// </summary>
|
||
private void StatsRefreshTimer_Tick(object sender, EventArgs e)
|
||
{
|
||
if (!isFormLoaded || isFormMessageStop || !pendingStatsRefresh)
|
||
return;
|
||
|
||
pendingStatsRefresh = false;
|
||
UpdateStatistics(lastDisplayCountForStats);
|
||
}
|
||
|
||
private void RequestStatisticsRefresh(int displayCount)
|
||
{
|
||
lastDisplayCountForStats = displayCount;
|
||
pendingStatsRefresh = true;
|
||
}
|
||
|
||
private void EnsureIpInFilter(string ipAddress)
|
||
{
|
||
if (cmbIpFilter == null || string.IsNullOrWhiteSpace(ipAddress))
|
||
return;
|
||
|
||
if (!cmbIpFilter.Items.Contains(ipAddress))
|
||
{
|
||
cmbIpFilter.Items.Add(ipAddress);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 向表格新增一条报文行(支持头部插入)
|
||
/// </summary>
|
||
private void AddMessageRow(CommunicationMessage msg, bool insertAtTop = true)
|
||
{
|
||
if (msg == null || dgvMessages == null)
|
||
return;
|
||
|
||
DataGridViewRow row;
|
||
if (insertAtTop)
|
||
{
|
||
dgvMessages.Rows.Insert(0,
|
||
msg.Timestamp.ToString("HH:mm:ss.fff"),
|
||
msg.Direction == MessageDirection.Send ? "发送" : "接收",
|
||
msg.IpAddress,
|
||
msg.Port,
|
||
msg.Length,
|
||
msg.RawData,
|
||
msg.StationId ?? "-",
|
||
msg.Type);
|
||
row = dgvMessages.Rows[0];
|
||
}
|
||
else
|
||
{
|
||
var index = dgvMessages.Rows.Add(
|
||
msg.Timestamp.ToString("HH:mm:ss.fff"),
|
||
msg.Direction == MessageDirection.Send ? "发送" : "接收",
|
||
msg.IpAddress,
|
||
msg.Port,
|
||
msg.Length,
|
||
msg.RawData,
|
||
msg.StationId ?? "-",
|
||
msg.Type);
|
||
row = dgvMessages.Rows[index];
|
||
}
|
||
|
||
if (msg.Direction == MessageDirection.Send)
|
||
{
|
||
row.DefaultCellStyle.BackColor = Color.FromArgb(232, 245, 233);
|
||
row.DefaultCellStyle.ForeColor = Color.FromArgb(46, 125, 50);
|
||
}
|
||
else
|
||
{
|
||
row.DefaultCellStyle.BackColor = Color.FromArgb(227, 242, 253);
|
||
row.DefaultCellStyle.ForeColor = Color.FromArgb(13, 71, 161);
|
||
}
|
||
|
||
while (dgvMessages.Rows.Count > MaxDisplayRows)
|
||
{
|
||
dgvMessages.Rows.RemoveAt(dgvMessages.Rows.Count - 1);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新统计信息
|
||
/// </summary>
|
||
private void UpdateStatistics(int displayCount)
|
||
{
|
||
try
|
||
{
|
||
if (lblStatistics == null || messageService == null)
|
||
return;
|
||
|
||
var allMessages = messageService.GetAllMessages();
|
||
if (allMessages == null)
|
||
return;
|
||
|
||
var sendCount = allMessages.Count(m => m.Direction == MessageDirection.Send);
|
||
var receiveCount = allMessages.Count(m => m.Direction == MessageDirection.Receive);
|
||
|
||
lblStatistics.Text = $"显示: {displayCount} | 总数: {allMessages.Count} | 发送: {sendCount} | 接收: {receiveCount}";
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"更新统计信息失败: {ex.Message}");
|
||
if (lblStatistics != null)
|
||
{
|
||
lblStatistics.Text = "统计信息加载失败";
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 新报文添加事件处理(线程安全)
|
||
/// </summary>
|
||
private void OnMessageAdded(object sender, CommunicationMessage message)
|
||
{
|
||
// 如果窗体还未加载完成,忽略此事件
|
||
if (!isFormLoaded || isFormMessageStop)
|
||
return;
|
||
|
||
try
|
||
{
|
||
if (message == null)
|
||
{
|
||
return;
|
||
}
|
||
lock (pendingMessagesLock)
|
||
{
|
||
pendingMessages.Enqueue(message);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"处理新报文失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析报文数据
|
||
/// </summary>
|
||
private void ParseMessage(CommunicationMessage message)
|
||
{
|
||
if (message == null || txtParsedData == null)
|
||
return;
|
||
|
||
try
|
||
{
|
||
var parsed = new System.Text.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(FormatHexString(message.RawData));
|
||
parsed.AppendLine();
|
||
parsed.AppendLine("=== 数据解析 ===");
|
||
|
||
// TODO: 根据实际协议进行解析
|
||
parsed.AppendLine();
|
||
if (message.Direction== MessageDirection.Send)
|
||
{
|
||
var sendDate = messageService.ParseSendRawData(message.RawData, message.Type);
|
||
parsed.AppendLine("示例解析:");
|
||
parsed.AppendLine($"充电指令:{sendDate.ChargeCommand}");
|
||
parsed.AppendLine($"发送电压:{sendDate.SetVoltage}");
|
||
parsed.AppendLine($"发送电流:{sendDate.SetCurrent}");
|
||
parsed.AppendLine($"车辆ID:{sendDate.CurrentVehicleId}");
|
||
parsed.AppendLine($"车辆电量:{sendDate.BatteryLevel}");
|
||
parsed.AppendLine($"车辆电压:{sendDate.CarVoltage}");
|
||
parsed.AppendLine($"车辆电流:{sendDate.CarCurrent}");
|
||
|
||
}
|
||
else
|
||
{
|
||
|
||
var recDate = messageService.ParseReceiveRawData(message.RawData, message.Type);
|
||
string mechanismStatus = (int)recDate.MechanismStatus == 1 ? "伸出" : (int)recDate.MechanismStatus == 2 ? "缩回" : (int)recDate.MechanismStatus == 3 ? "运动中" : recDate.MechanismStatus.ToString();
|
||
parsed.AppendLine("示例解析:");
|
||
parsed.AppendLine($"机构状态:{mechanismStatus}");
|
||
parsed.AppendLine($"实时电压:{recDate.RealTimeVoltage}");
|
||
parsed.AppendLine($"实时电流:{recDate.RealTimeCurrent}");
|
||
parsed.AppendLine($"充电量: {recDate.BatteryAH}");
|
||
parsed.AppendLine($"是否报警:{recDate.HasAlarm}");
|
||
parsed.AppendLine($"充电状态:{recDate.Status.ToString()}");
|
||
|
||
}
|
||
|
||
|
||
|
||
txtParsedData.Text = parsed.ToString();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
txtParsedData.Text = $"解析失败: {ex.Message}";
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 格式化十六进制字符串
|
||
/// </summary>
|
||
private string FormatHexString(string hexData)
|
||
{
|
||
if (string.IsNullOrEmpty(hexData))
|
||
return string.Empty;
|
||
|
||
var formatted = new System.Text.StringBuilder();
|
||
for (int i = 0; i < hexData.Length; i += 2)
|
||
{
|
||
if (i > 0 && i % 32 == 0)
|
||
formatted.AppendLine();
|
||
else if (i > 0)
|
||
formatted.Append(" ");
|
||
|
||
if (i + 1 < hexData.Length)
|
||
formatted.Append(hexData.Substring(i, 2));
|
||
else
|
||
formatted.Append(hexData[i]);
|
||
}
|
||
return formatted.ToString();
|
||
}
|
||
|
||
// ==================== 事件处理 ====================
|
||
|
||
private void cmbIpFilter_SelectedIndexChanged(object sender, EventArgs e)
|
||
{
|
||
try
|
||
{
|
||
LoadMessages();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"筛选改变失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
private void dgvMessages_SelectionChanged(object sender, EventArgs e)
|
||
{
|
||
try
|
||
{
|
||
if (dgvMessages.SelectedRows.Count > 0)
|
||
{
|
||
var row = dgvMessages.SelectedRows[0];
|
||
var rawData = row.Cells[5].Value?.ToString();
|
||
var ipAddress = row.Cells[2].Value?.ToString();
|
||
var port = int.Parse(row.Cells[3].Value?.ToString() ?? "0");
|
||
var timeStr = row.Cells[0].Value?.ToString();
|
||
var directionStr = row.Cells[1].Value?.ToString();
|
||
var stationId = row.Cells[6].Value?.ToString();
|
||
var type = row.Cells[7].Value?.ToString();
|
||
|
||
// 构造消息对象用于解析
|
||
var message = new CommunicationMessage
|
||
{
|
||
RawData = rawData,
|
||
IpAddress = ipAddress,
|
||
Port = port,
|
||
Direction = directionStr == "发送" ? MessageDirection.Send : MessageDirection.Receive,
|
||
StationId = stationId == "-" ? null : stationId,
|
||
Length = rawData.Split(' ')?.Length ?? 0,
|
||
Type=type,
|
||
|
||
};
|
||
|
||
if (DateTime.TryParse(timeStr, out DateTime timestamp))
|
||
{
|
||
message.Timestamp = timestamp;
|
||
}
|
||
|
||
ParseMessage(message);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"选择报文失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
private void btnRefresh_Click(object sender, EventArgs e)
|
||
{
|
||
try
|
||
{
|
||
RefreshIpFilter();
|
||
LoadMessages();
|
||
RequestStatisticsRefresh(dgvMessages?.Rows.Count ?? 0);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show($"刷新失败: {ex.Message}", "错误",
|
||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
}
|
||
}
|
||
|
||
private void btnClear_Click(object sender, EventArgs e)
|
||
{
|
||
try
|
||
{
|
||
var result = MessageBox.Show(
|
||
"确定要清空所有报文记录吗?",
|
||
"确认清空",
|
||
MessageBoxButtons.YesNo,
|
||
MessageBoxIcon.Question);
|
||
|
||
if (result == DialogResult.Yes)
|
||
{
|
||
messageService.Clear();
|
||
RefreshIpFilter();
|
||
LoadMessages();
|
||
if (txtParsedData != null)
|
||
{
|
||
txtParsedData.Clear();
|
||
}
|
||
dgvMessages.Rows.Clear();
|
||
RequestStatisticsRefresh(0);
|
||
}
|
||
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show($"清空报文失败: {ex.Message}", "错误",
|
||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
}
|
||
}
|
||
|
||
private void btnClose_Click(object sender, EventArgs e)
|
||
{
|
||
this.Close();
|
||
}
|
||
|
||
private void CommunicationMonitorForm_FormClosing(object sender, FormClosingEventArgs e)
|
||
{
|
||
// 取消订阅事件
|
||
messageService.MessageAdded -= OnMessageAdded;
|
||
uiFlushTimer.Stop();
|
||
uiFlushTimer.Dispose();
|
||
statsRefreshTimer.Stop();
|
||
statsRefreshTimer.Dispose();
|
||
}
|
||
|
||
private void button1_Click(object sender, EventArgs e)
|
||
{
|
||
isFormMessageStop = !isFormMessageStop;
|
||
if (sender is Button pauseButton)
|
||
{
|
||
pauseButton.Text = isFormMessageStop ? "继续" : "暂停";
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|