Release v1.1.1.1: Anti-Theft buttons update, BMS history chg_wait column, and wait for charging status display
This commit is contained in:
369
LFP_Manager/Forms/fmxPacketLog.cs
Normal file
369
LFP_Manager/Forms/fmxPacketLog.cs
Normal file
@@ -0,0 +1,369 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using DevExpress.XtraEditors;
|
||||
using DevExpress.XtraGrid.Views.Grid;
|
||||
using LFP_Manager.Function;
|
||||
|
||||
namespace LFP_Manager.Forms
|
||||
{
|
||||
public partial class fmxPacketLog : DevExpress.XtraEditors.XtraForm
|
||||
{
|
||||
private DataTable packetTable = null;
|
||||
private int packetIndex = 0;
|
||||
private int txCount = 0;
|
||||
private int rxCount = 0;
|
||||
private bool isCapturing = true;
|
||||
private const int MAX_LOG_COUNT = 5000;
|
||||
|
||||
public fmxPacketLog()
|
||||
{
|
||||
InitializeComponent();
|
||||
InitDataTable();
|
||||
}
|
||||
|
||||
private void InitDataTable()
|
||||
{
|
||||
packetTable = new DataTable();
|
||||
packetTable.Columns.Add("no", typeof(int));
|
||||
packetTable.Columns.Add("time", typeof(string));
|
||||
packetTable.Columns.Add("dir", typeof(string));
|
||||
packetTable.Columns.Add("dev_id", typeof(string));
|
||||
packetTable.Columns.Add("func", typeof(string));
|
||||
packetTable.Columns.Add("len", typeof(int));
|
||||
packetTable.Columns.Add("hex", typeof(string));
|
||||
packetTable.Columns.Add("desc", typeof(string));
|
||||
packetTable.Columns.Add("crc", typeof(string));
|
||||
packetTable.Columns.Add("raw_bytes", typeof(object));
|
||||
|
||||
gcPacketLog.DataSource = packetTable;
|
||||
ApplyFilter();
|
||||
}
|
||||
|
||||
public void AddPacketLog(DateTime timestamp, bool isTx, byte[] data, string description)
|
||||
{
|
||||
if (!isCapturing || data == null || data.Length == 0) return;
|
||||
|
||||
if (this.InvokeRequired)
|
||||
{
|
||||
this.BeginInvoke(new Action(() => AddPacketLogRow(timestamp, isTx, data, description)));
|
||||
}
|
||||
else
|
||||
{
|
||||
AddPacketLogRow(timestamp, isTx, data, description);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddPacketLogRow(DateTime timestamp, bool isTx, byte[] data, string description)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (packetTable == null) return;
|
||||
|
||||
// Max rows management
|
||||
if (packetTable.Rows.Count >= MAX_LOG_COUNT)
|
||||
{
|
||||
for (int i = 0; i < 500; i++)
|
||||
{
|
||||
if (packetTable.Rows.Count > 0)
|
||||
packetTable.Rows.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
packetIndex++;
|
||||
if (isTx) txCount++;
|
||||
else rxCount++;
|
||||
|
||||
string dir = isTx ? "TX" : "RX";
|
||||
string devId = data.Length > 0 ? string.Format("0x{0:X2}", data[0]) : "-";
|
||||
string func = data.Length > 1 ? string.Format("0x{0:X2}", data[1]) : "-";
|
||||
int len = data.Length;
|
||||
|
||||
StringBuilder sbHex = new StringBuilder(data.Length * 3);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
sbHex.AppendFormat("{0:X2} ", data[i]);
|
||||
}
|
||||
string hex = sbHex.ToString().TrimEnd();
|
||||
|
||||
// CRC Check
|
||||
string crcResult = "OK";
|
||||
if (data.Length >= 4)
|
||||
{
|
||||
byte[] calcCrc = csSerialCommFunction.GetCRC(data, data.Length - 2);
|
||||
byte rxCrcH = data[data.Length - 2];
|
||||
byte rxCrcL = data[data.Length - 1];
|
||||
|
||||
if (calcCrc[1] != rxCrcH || calcCrc[0] != rxCrcL)
|
||||
{
|
||||
crcResult = "ERR";
|
||||
}
|
||||
}
|
||||
|
||||
DataRow row = packetTable.NewRow();
|
||||
row["no"] = packetIndex;
|
||||
row["time"] = timestamp.ToString("HH:mm:ss.fff");
|
||||
row["dir"] = dir;
|
||||
row["dev_id"] = devId;
|
||||
row["func"] = func;
|
||||
row["len"] = len;
|
||||
row["hex"] = hex;
|
||||
row["desc"] = description;
|
||||
row["crc"] = crcResult;
|
||||
row["raw_bytes"] = data;
|
||||
|
||||
packetTable.Rows.Add(row);
|
||||
|
||||
lblStatus.Text = string.Format("Total: {0} | TX: {1} | RX: {2}", packetIndex, txCount, rxCount);
|
||||
|
||||
if (chkAutoScroll.Checked && gvPacketLog.RowCount > 0)
|
||||
{
|
||||
gvPacketLog.MoveLast();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void gvPacketLog_FocusedRowChanged(object sender, DevExpress.XtraGrid.Views.Base.FocusedRowChangedEventArgs e)
|
||||
{
|
||||
DataRow row = gvPacketLog.GetFocusedDataRow();
|
||||
if (row == null)
|
||||
{
|
||||
txtDetail.Text = string.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] rawBytes = row["raw_bytes"] as byte[];
|
||||
string dir = row["dir"].ToString();
|
||||
string desc = row["desc"].ToString();
|
||||
string time = row["time"].ToString();
|
||||
|
||||
txtDetail.Text = AnalyzePacket(rawBytes, dir, desc, time);
|
||||
}
|
||||
|
||||
private string AnalyzePacket(byte[] data, string dir, string desc, string time)
|
||||
{
|
||||
if (data == null || data.Length == 0) return "No Data";
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine("================================================================================");
|
||||
sb.AppendLine(string.Format("[{0}] {1} (Length: {2} bytes) - {3}", dir, desc, data.Length, time));
|
||||
sb.AppendLine("================================================================================");
|
||||
|
||||
// 1. Raw HEX Dump formatted
|
||||
sb.AppendLine("[RAW HEX DUMP]");
|
||||
for (int i = 0; i < data.Length; i += 16)
|
||||
{
|
||||
sb.AppendFormat("{0:X4}: ", i);
|
||||
int count = Math.Min(16, data.Length - i);
|
||||
for (int j = 0; j < count; j++)
|
||||
{
|
||||
sb.AppendFormat("{0:X2} ", data[i + j]);
|
||||
}
|
||||
sb.AppendLine();
|
||||
}
|
||||
sb.AppendLine();
|
||||
|
||||
// 2. Modbus Protocol Interpretation
|
||||
sb.AppendLine("[MODBUS FRAME INTERPRETATION]");
|
||||
if (data.Length >= 2)
|
||||
{
|
||||
byte devId = data[0];
|
||||
byte funcCode = data[1];
|
||||
|
||||
sb.AppendLine(string.Format(" - Device (Slave) Address : 0x{0:X2} ({0})", devId));
|
||||
sb.AppendLine(string.Format(" - Function Code : 0x{0:X2} ({1})", funcCode, GetFunctionCodeName(funcCode)));
|
||||
|
||||
if (dir == "TX")
|
||||
{
|
||||
if (data.Length >= 6)
|
||||
{
|
||||
ushort regAddr = (ushort)((data[2] << 8) | data[3]);
|
||||
ushort regCount = (ushort)((data[4] << 8) | data[5]);
|
||||
sb.AppendLine(string.Format(" - Target Register Address: 0x{0:X4} ({0})", regAddr));
|
||||
sb.AppendLine(string.Format(" - Register Count / Value : 0x{0:X4} ({0})", regCount));
|
||||
}
|
||||
}
|
||||
else // RX
|
||||
{
|
||||
if (funcCode == 0x03 && data.Length >= 3)
|
||||
{
|
||||
byte byteCount = data[2];
|
||||
sb.AppendLine(string.Format(" - Data Byte Count : {0} bytes ({1} registers)", byteCount, byteCount / 2));
|
||||
|
||||
int regIndex = 0;
|
||||
for (int p = 3; p + 1 < data.Length - 2; p += 2)
|
||||
{
|
||||
ushort val = (ushort)((data[p] << 8) | data[p + 1]);
|
||||
sb.AppendLine(string.Format(" [Reg +{0:D2}] Offset 0x{1:X2} : 0x{2:X4} ({3})", regIndex++, p - 3, val, (short)val));
|
||||
}
|
||||
}
|
||||
else if (funcCode > 0x80)
|
||||
{
|
||||
byte errCode = data.Length > 2 ? data[2] : (byte)0;
|
||||
sb.AppendLine(string.Format(" - Modbus Exception Code : 0x{0:X2} ({1})", errCode, GetExceptionName(errCode)));
|
||||
}
|
||||
}
|
||||
|
||||
// CRC Check
|
||||
if (data.Length >= 4)
|
||||
{
|
||||
byte[] calcCrc = csSerialCommFunction.GetCRC(data, data.Length - 2);
|
||||
byte rxCrcH = data[data.Length - 2];
|
||||
byte rxCrcL = data[data.Length - 1];
|
||||
bool crcOk = (calcCrc[1] == rxCrcH && calcCrc[0] == rxCrcL);
|
||||
|
||||
sb.AppendLine(string.Format(" - CRC16 Checksum : [H:0x{0:X2}, L:0x{1:X2}] (Calc: 0x{2:X2}{3:X2}) -> {4}",
|
||||
rxCrcH, rxCrcL, calcCrc[1], calcCrc[0], crcOk ? "VALID (OK)" : "INVALID (MISMATCH)"));
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private string GetFunctionCodeName(byte func)
|
||||
{
|
||||
switch (func)
|
||||
{
|
||||
case 0x01: return "Read Coil Status (0x01)";
|
||||
case 0x02: return "Read Input Status (0x02)";
|
||||
case 0x03: return "Read Holding Registers (0x03)";
|
||||
case 0x04: return "Read Input Registers (0x04)";
|
||||
case 0x05: return "Force Single Coil (0x05)";
|
||||
case 0x06: return "Preset Single Register (0x06)";
|
||||
case 0x10: return "Preset Multiple Registers (0x10)";
|
||||
default:
|
||||
if (func > 0x80) return string.Format("Exception Response (0x{0:X2})", func);
|
||||
return string.Format("Custom / Unknown (0x{0:X2})", func);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetExceptionName(byte err)
|
||||
{
|
||||
switch (err)
|
||||
{
|
||||
case 0x01: return "Illegal Function";
|
||||
case 0x02: return "Illegal Data Address";
|
||||
case 0x03: return "Illegal Data Value";
|
||||
case 0x04: return "Slave Device Failure";
|
||||
case 0x05: return "Acknowledge";
|
||||
case 0x06: return "Slave Device Busy";
|
||||
default: return "Unknown Exception";
|
||||
}
|
||||
}
|
||||
|
||||
private void gvPacketLog_RowStyle(object sender, RowStyleEventArgs e)
|
||||
{
|
||||
if (e.RowHandle < 0) return;
|
||||
|
||||
DataRow row = gvPacketLog.GetDataRow(e.RowHandle);
|
||||
if (row == null) return;
|
||||
|
||||
string dir = row["dir"].ToString();
|
||||
string crc = row["crc"].ToString();
|
||||
|
||||
if (crc == "ERR")
|
||||
{
|
||||
e.Appearance.ForeColor = Color.Red;
|
||||
e.Appearance.Font = new Font(e.Appearance.Font, FontStyle.Bold);
|
||||
}
|
||||
else if (dir == "TX")
|
||||
{
|
||||
e.Appearance.ForeColor = Color.DarkBlue;
|
||||
}
|
||||
else if (dir == "RX")
|
||||
{
|
||||
e.Appearance.ForeColor = Color.DarkGreen;
|
||||
}
|
||||
}
|
||||
|
||||
private void btnCapture_Click(object sender, EventArgs e)
|
||||
{
|
||||
isCapturing = !isCapturing;
|
||||
btnCapture.Text = isCapturing ? "Pause" : "Resume";
|
||||
btnCapture.Appearance.ForeColor = isCapturing ? Color.Black : Color.DarkRed;
|
||||
}
|
||||
|
||||
private void btnClear_Click(object sender, EventArgs e)
|
||||
{
|
||||
packetTable?.Rows.Clear();
|
||||
packetIndex = 0;
|
||||
txCount = 0;
|
||||
rxCount = 0;
|
||||
lblStatus.Text = "Total: 0 | TX: 0 | RX: 0";
|
||||
txtDetail.Text = string.Empty;
|
||||
}
|
||||
|
||||
private void cmbFilter_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
ApplyFilter();
|
||||
}
|
||||
|
||||
private void ApplyFilter()
|
||||
{
|
||||
if (packetTable == null) return;
|
||||
|
||||
string selected = cmbFilter.Text;
|
||||
DataView dv = packetTable.DefaultView;
|
||||
|
||||
if (selected == "TX Only")
|
||||
{
|
||||
dv.RowFilter = "dir = 'TX'";
|
||||
}
|
||||
else if (selected == "RX Only")
|
||||
{
|
||||
dv.RowFilter = "dir = 'RX'";
|
||||
}
|
||||
else if (selected == "Error Only")
|
||||
{
|
||||
dv.RowFilter = "crc = 'ERR'";
|
||||
}
|
||||
else
|
||||
{
|
||||
dv.RowFilter = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private void btnExport_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (packetTable == null || packetTable.Rows.Count == 0)
|
||||
{
|
||||
MessageBox.Show("No packet logs to export.", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
SaveFileDialog sDialog = new SaveFileDialog();
|
||||
sDialog.Title = "Export Packet Logs";
|
||||
sDialog.DefaultExt = "xls";
|
||||
sDialog.Filter = "Excel files (*.xls)|*.xls|All files (*.*)|*.*";
|
||||
|
||||
if (sDialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
try
|
||||
{
|
||||
DataTable exportTable = packetTable.DefaultView.ToTable(false, "no", "time", "dir", "dev_id", "func", "len", "hex", "desc", "crc");
|
||||
csExcelExport.ExportToExcelExt(exportTable, sDialog.FileName);
|
||||
MessageBox.Show("Complete Exporting Packet Log File.", "Result", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(ex.Message, "Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fmxPacketLog_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
// Just hide on close so it can be re-opened quickly
|
||||
if (e.CloseReason == CloseReason.UserClosing)
|
||||
{
|
||||
e.Cancel = true;
|
||||
this.Hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user