556 lines
19 KiB
C#
556 lines
19 KiB
C#
using DevExpress.Utils;
|
||
using DevExpress.XtraEditors.Repository;
|
||
using DevExpress.XtraGrid;
|
||
using DevExpress.XtraGrid.Columns;
|
||
using DevExpress.XtraGrid.Views.Grid;
|
||
|
||
using LFP_Manager.DataStructure;
|
||
using LFP_Manager.Function;
|
||
using LFP_Manager.Utils;
|
||
|
||
using System;
|
||
using System.Data;
|
||
using System.Windows.Forms;
|
||
|
||
namespace LFP_Manager.Forms
|
||
{
|
||
public partial class fmxHistory : DevExpress.XtraEditors.XtraForm
|
||
{
|
||
#region VARIABLES
|
||
|
||
private int SystemId = 0;
|
||
private CommConfig Config;
|
||
|
||
private byte[] rData = null;
|
||
private ushort curr_addr = 0;
|
||
private int recv_data_len = 0;
|
||
|
||
private int hist_no = 0;
|
||
private bool stopReq = true;
|
||
|
||
private int cell_qty = 16;
|
||
private int temp_qty = 4;
|
||
|
||
private int req_timeoutCount = 0;
|
||
|
||
private DateTime ReqDateTime;
|
||
|
||
private DataTable BmsHistoryTable = null;
|
||
|
||
public event SendDataUartEvent OnSendUartData = null;
|
||
|
||
#endregion
|
||
|
||
#region CONTRUCTORS
|
||
|
||
public fmxHistory(CommConfig aConfig, int systemId)
|
||
{
|
||
InitializeComponent();
|
||
|
||
Config = aConfig;
|
||
|
||
switch (Config.UartModelIndex)
|
||
{
|
||
case csConstData.MODEL_INDEX.LFPM_124050D:
|
||
recv_data_len = 112;
|
||
cell_qty = 39;
|
||
temp_qty = 8;
|
||
break;
|
||
default:
|
||
recv_data_len = 58;
|
||
cell_qty = 16;
|
||
temp_qty = 4;
|
||
break;
|
||
}
|
||
|
||
ucHistroy1.SetCommCofig(Config);
|
||
SystemId = systemId;
|
||
|
||
CreateHistoryColumn(ref BmsHistoryTable);
|
||
BuildHistoryGridColumns(gcBmsHistory, gvBmsHistory, BmsHistoryTable);
|
||
|
||
gvBmsHistory.Appearance.HeaderPanel.TextOptions.HAlignment = HorzAlignment.Center;
|
||
gvBmsHistory.Appearance.HeaderPanel.TextOptions.VAlignment = VertAlignment.Center;
|
||
|
||
switch (Config.CommType)
|
||
{
|
||
case csConstData.CommType.COMM_UART:
|
||
pgBmsHistory.PageEnabled = true;
|
||
//tabHistory.TabPages.Remove(pgBmsHistory);
|
||
break;
|
||
case csConstData.CommType.COMM_RS485:
|
||
pgBmsHistory.PageEnabled = false;
|
||
tabHistory.TabPages.Remove(pgBmsHistory);
|
||
break;
|
||
case csConstData.CommType.COMM_SNMP:
|
||
pgBmsHistory.PageEnabled = false;
|
||
tabHistory.TabPages.Remove(pgBmsHistory);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region INITIALIZE COMPONENT
|
||
|
||
private void CreateHistoryColumn(ref DataTable hDT)
|
||
{
|
||
hDT = new DataTable();
|
||
|
||
hDT.Columns.Add("no", typeof(int));
|
||
hDT.Columns.Add("datetime", typeof(DateTime));
|
||
hDT.Columns.Add("status", typeof(int));
|
||
hDT.Columns.Add("warning", typeof(string));
|
||
hDT.Columns.Add("protect", typeof(string));
|
||
hDT.Columns.Add("error", typeof(string));
|
||
|
||
hDT.Columns.Add("voltage", typeof(decimal));
|
||
hDT.Columns.Add("current", typeof(decimal));
|
||
hDT.Columns.Add("SOC", typeof(decimal));
|
||
|
||
hDT.Columns.Add("pcb_t", typeof(decimal));
|
||
hDT.Columns.Add("amb_t", typeof(decimal));
|
||
|
||
for (int i = 0; i < temp_qty; i++)
|
||
{
|
||
hDT.Columns.Add(string.Format("temp_{0:00}", i + 1), typeof(decimal));
|
||
}
|
||
for (int i = 0; i < cell_qty; i++)
|
||
{
|
||
hDT.Columns.Add(string.Format("cell_v_{0:00}", i + 1), typeof(decimal));
|
||
}
|
||
gcBmsHistory.DataSource = hDT;
|
||
}
|
||
|
||
private void BuildHistoryGridColumns(GridControl grid, GridView view, DataTable dt)
|
||
{
|
||
grid.BeginUpdate();
|
||
view.BeginUpdate();
|
||
|
||
try
|
||
{
|
||
// 1️⃣ DataSource 바인딩
|
||
grid.DataSource = dt;
|
||
|
||
// 2️⃣ 기존 컬럼 제거
|
||
view.Columns.Clear();
|
||
|
||
// 3️⃣ RepositoryItems 준비
|
||
|
||
RepositoryItemTextEdit repoText = new RepositoryItemTextEdit
|
||
{
|
||
ReadOnly = true
|
||
};
|
||
|
||
RepositoryItemSpinEdit repoInt = new RepositoryItemSpinEdit
|
||
{
|
||
IsFloatValue = false,
|
||
Mask = { EditMask = "N0", UseMaskAsDisplayFormat = true }
|
||
};
|
||
|
||
RepositoryItemSpinEdit repoF1 = new RepositoryItemSpinEdit
|
||
{
|
||
IsFloatValue = true,
|
||
Mask = { EditMask = "F1", UseMaskAsDisplayFormat = true }
|
||
};
|
||
|
||
RepositoryItemSpinEdit repoF2 = new RepositoryItemSpinEdit
|
||
{
|
||
IsFloatValue = true,
|
||
Mask = { EditMask = "F2", UseMaskAsDisplayFormat = true }
|
||
};
|
||
|
||
RepositoryItemSpinEdit repoF3 = new RepositoryItemSpinEdit
|
||
{
|
||
IsFloatValue = true,
|
||
Mask = { EditMask = "F3", UseMaskAsDisplayFormat = true }
|
||
};
|
||
|
||
RepositoryItemDateEdit repoDate = new RepositoryItemDateEdit
|
||
{
|
||
Mask = { EditMask = "yyyy-MM-dd HH:mm:ss", UseMaskAsDisplayFormat = true }
|
||
};
|
||
|
||
grid.RepositoryItems.AddRange(new RepositoryItem[]
|
||
{
|
||
repoText, repoInt, repoF1, repoF2, repoF3, repoDate
|
||
});
|
||
|
||
int visibleIndex = 0;
|
||
|
||
// 4️⃣ DataTable 컬럼 기준 GridColumn 생성
|
||
foreach (DataColumn dc in dt.Columns)
|
||
{
|
||
GridColumn col = new GridColumn
|
||
{
|
||
FieldName = dc.ColumnName,
|
||
Name = "gc_" + dc.ColumnName,
|
||
Caption = GetHistoryColumnCaption(dc.ColumnName), // ⭐ 헤더 변경
|
||
Visible = true,
|
||
VisibleIndex = visibleIndex++
|
||
};
|
||
|
||
string name = dc.ColumnName.ToLower();
|
||
|
||
// ───── 타입/이름 기준 포맷 규칙 ─────
|
||
|
||
if (name == "no")
|
||
{
|
||
col.ColumnEdit = repoInt;
|
||
col.DisplayFormat.FormatType = FormatType.Numeric;
|
||
col.DisplayFormat.FormatString = "N0";
|
||
col.AppearanceCell.TextOptions.HAlignment = HorzAlignment.Center;
|
||
}
|
||
else if (name == "datetime")
|
||
{
|
||
col.ColumnEdit = repoDate;
|
||
col.DisplayFormat.FormatType = FormatType.DateTime;
|
||
col.DisplayFormat.FormatString = "yyyy-MM-dd HH:mm:ss";
|
||
}
|
||
else if (name == "status")
|
||
{
|
||
col.ColumnEdit = repoText;
|
||
// ✅ status는 표시가 문자열이므로 가운데 정렬 강제
|
||
col.AppearanceCell.Options.UseTextOptions = true; // ⭐⭐⭐ 핵심 ⭐⭐⭐
|
||
col.AppearanceCell.TextOptions.HAlignment = HorzAlignment.Center;
|
||
|
||
// (선택) 숫자 표시 포맷/에디터 굳이 필요 없으면 빼도 됨
|
||
// col.ColumnEdit = repoInt;
|
||
}
|
||
else if (name == "voltage" || name == "current" || name == "soc")
|
||
{
|
||
col.ColumnEdit = repoF2;
|
||
col.DisplayFormat.FormatType = FormatType.Numeric;
|
||
col.DisplayFormat.FormatString = "0.00";
|
||
}
|
||
else if (name == "pcb_t" || name == "amb_t" || name.StartsWith("temp_"))
|
||
{
|
||
col.ColumnEdit = repoF1;
|
||
col.DisplayFormat.FormatType = FormatType.Numeric;
|
||
col.DisplayFormat.FormatString = "0.0";
|
||
}
|
||
else if (name.StartsWith("cell_v_"))
|
||
{
|
||
col.ColumnEdit = repoF3;
|
||
col.DisplayFormat.FormatType = FormatType.Numeric;
|
||
col.DisplayFormat.FormatString = "0.000";
|
||
}
|
||
else
|
||
{
|
||
// warning / protect / error (string)
|
||
col.ColumnEdit = repoText;
|
||
col.AppearanceCell.Options.UseTextOptions = true; // ⭐⭐⭐ 핵심 ⭐⭐⭐
|
||
col.AppearanceCell.TextOptions.HAlignment = HorzAlignment.Center;
|
||
}
|
||
|
||
view.Columns.Add(col);
|
||
}
|
||
|
||
// 5️⃣ 전체 헤더 가운데 정렬 (요청하신 설정)
|
||
view.Appearance.HeaderPanel.TextOptions.HAlignment = DevExpress.Utils.HorzAlignment.Center;
|
||
view.Appearance.HeaderPanel.TextOptions.VAlignment = DevExpress.Utils.VertAlignment.Center;
|
||
|
||
// 6️⃣ Grid 기본 옵션
|
||
view.OptionsView.ColumnAutoWidth = false;
|
||
view.OptionsView.ShowGroupPanel = false;
|
||
view.OptionsBehavior.Editable = false; // History Grid → 읽기 전용 권장
|
||
}
|
||
finally
|
||
{
|
||
view.EndUpdate();
|
||
grid.EndUpdate();
|
||
}
|
||
}
|
||
|
||
private string GetHistoryColumnCaption(string fieldName)
|
||
{
|
||
string name = fieldName.ToLower();
|
||
|
||
if (name == "no") return "No";
|
||
if (name == "datetime") return "Date / Time";
|
||
if (name == "status") return "Status";
|
||
|
||
if (name == "warning") return "Warning Flags";
|
||
if (name == "protect") return "Protect Flags";
|
||
if (name == "error") return "Error Flags";
|
||
|
||
if (name == "voltage") return "Pack Voltage (V)";
|
||
if (name == "current") return "Pack Current (A)";
|
||
if (name == "soc") return "SOC (%)";
|
||
|
||
if (name == "pcb_t") return "PCB Temp (°C)";
|
||
if (name == "amb_t") return "Ambient Temp (°C)";
|
||
|
||
if (name.StartsWith("temp_"))
|
||
{
|
||
int idx = int.Parse(name.Substring(5));
|
||
return $"Temp {idx} (°C)";
|
||
}
|
||
|
||
if (name.StartsWith("cell_v_"))
|
||
{
|
||
int idx = int.Parse(name.Substring(7));
|
||
return $"Cell {idx} Voltage (V)";
|
||
}
|
||
|
||
// 기본: 컬럼명 그대로 (Fallback)
|
||
return fieldName;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region PUBLIC FUNCTIONS
|
||
public void RecvUartData(byte[] data)
|
||
{
|
||
if (InvokeRequired)
|
||
{
|
||
BeginInvoke(new Action(() => RecvUartData(data)));
|
||
return;
|
||
}
|
||
|
||
rData = data;
|
||
|
||
LcGrResult.Text = $"Result ({rData.Length})";
|
||
|
||
if ((rData != null) && (rData.Length == recv_data_len))
|
||
{
|
||
try
|
||
{
|
||
req_timeoutCount = 0;
|
||
|
||
DataRow hRow = BmsHistoryTable.NewRow();
|
||
|
||
short[] shorts = csUtils.ByteArrTo2ShortSafe(data);
|
||
|
||
int pos = 0;
|
||
int yy, MM, dd, HH, mm, ss;
|
||
Int32 iDateTime = (rData[3 + 2] << 24) | (rData[3 + 3] << 16) | (rData[3 + 0] << 8) | (rData[3 + 1] << 0);
|
||
//Int32 iDateTime = (Int32)((shorts[pos + 1] << 16) | (shorts[pos + 0] << 0));
|
||
yy = (iDateTime >> 26) & 0x003F;
|
||
MM = (iDateTime >> 22) & 0x000F;
|
||
dd = (iDateTime >> 17) & 0x001F;
|
||
HH = (iDateTime >> 12) & 0x001F;
|
||
mm = (iDateTime >> 6) & 0x003F;
|
||
ss = (iDateTime >> 0) & 0x003F;
|
||
|
||
hRow["no"] = hist_no;
|
||
string stDateTime = string.Format("{0}-{1:00}-{2:00} {3:00}:{4:00}:{5:00}"
|
||
, yy
|
||
, MM
|
||
, dd
|
||
, HH
|
||
, mm
|
||
, ss
|
||
);
|
||
|
||
DateTime aDateTime;
|
||
|
||
if (MM < 1 || MM > 12 || dd < 1 || dd > 31 || HH > 23 || mm > 59 || ss > 59)
|
||
aDateTime = DateTime.MinValue; // 또는 DateTime.MinValue 넣기
|
||
else
|
||
aDateTime = Convert.ToDateTime(stDateTime);
|
||
|
||
hRow["datetime"] = aDateTime;
|
||
|
||
pos = 2;
|
||
hRow["status"] = shorts[pos++];
|
||
hRow["warning"] = string.Format("{0:X4}", shorts[pos++]);
|
||
hRow["protect"] = string.Format("{0:X4}", shorts[pos++]);
|
||
hRow["error"] = string.Format("{0:X4}", shorts[pos++]);
|
||
hRow["voltage"] = shorts[pos++] / 100m;
|
||
hRow["current"] = shorts[pos++] / 100m;
|
||
hRow["SOC"] = shorts[pos++] / 1m;
|
||
|
||
hRow["pcb_t"] = (byte)(shorts[pos] >> 8) / 1m;
|
||
hRow["amb_t"] = (byte)(shorts[pos] >> 0) / 1m;
|
||
pos++;
|
||
|
||
// Temperature
|
||
for (int i = 0; i < (temp_qty / 2); i++)
|
||
{
|
||
hRow[string.Format("temp_{0:00}", (i * 2) + 1)] = Convert.ToDouble((byte)(shorts[pos] >> 8));
|
||
hRow[string.Format("temp_{0:00}", (i * 2) + 2)] = Convert.ToDouble((byte)(shorts[pos++] >> 0));
|
||
}
|
||
// Cell Voltage
|
||
for (int i = 0; i < cell_qty; i++)
|
||
{
|
||
hRow[string.Format("cell_v_{0:00}", i + 1)] = Convert.ToDecimal(shorts[pos++]) / 1000m;
|
||
}
|
||
//hRow["SOH"] = shorts[pos++] / 1m;
|
||
|
||
BmsHistoryTable.Rows.Add(hRow);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
MessageBox.Show(e.Message);
|
||
}
|
||
}
|
||
|
||
hist_no++;
|
||
curr_addr += 0x20;
|
||
RequestHistoryData(curr_addr);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region BUTTON EVENT
|
||
|
||
private void btnClose_Click(object sender, EventArgs e)
|
||
{
|
||
Close();
|
||
}
|
||
|
||
private void btnBmsHistoryReq_Click(object sender, EventArgs e)
|
||
{
|
||
if (stopReq)
|
||
{
|
||
BmsHistoryTable.Rows.Clear();
|
||
|
||
stopReq = false;
|
||
hist_no = 1;
|
||
curr_addr = 0x0200;
|
||
TmrCheckReq.Enabled = true;
|
||
|
||
RequestHistoryData(curr_addr);
|
||
|
||
btnBmsHistoryReq.Text = "STOP";
|
||
}
|
||
else
|
||
{
|
||
StopAndFinish("User Stop Request");
|
||
}
|
||
}
|
||
|
||
private void RequestHistoryData(ushort haddr)
|
||
{
|
||
byte[] crc;
|
||
ushort addr = haddr;
|
||
|
||
// Normal mode --> Bootloader mode
|
||
byte[] sdata = new byte[8];
|
||
sdata[0] = (byte)SystemId; // Dev Address
|
||
sdata[1] = 0x03; // Function Code
|
||
sdata[2] = (byte)(addr >> 8); // Address H
|
||
sdata[3] = (byte)(addr >> 0); // Address L
|
||
sdata[4] = 0x00; // Number of Register H
|
||
sdata[5] = 0x35; // Number of Register L
|
||
|
||
crc = csSerialCommFunction.GetCRC(sdata, 6);
|
||
|
||
sdata[6] = crc[1]; // CRCH
|
||
sdata[7] = crc[0]; // CRCL
|
||
|
||
if (stopReq == false)
|
||
{
|
||
OnSendUartData?.Invoke(addr, sdata, false, 1);
|
||
ReqDateTime = DateTime.Now;
|
||
}
|
||
}
|
||
|
||
private void StopAndFinish(string reason = null)
|
||
{
|
||
stopReq = true;
|
||
TmrCheckReq.Enabled = false;
|
||
btnBmsHistoryReq.Text = "Request";
|
||
|
||
req_timeoutCount = 0;
|
||
|
||
gvBmsHistory.BestFitColumns();
|
||
gvBmsHistory.ClearSelection();
|
||
|
||
if (!string.IsNullOrEmpty(reason))
|
||
MessageBox.Show($"Finished: {reason}");
|
||
else
|
||
MessageBox.Show("Finished BMS History Read");
|
||
}
|
||
|
||
private void btnExportExcel_Click(object sender, EventArgs e)
|
||
{
|
||
SaveFileDialog sDialog = new SaveFileDialog();
|
||
sDialog.Title = "Select save file";
|
||
sDialog.DefaultExt = "xlsx";
|
||
sDialog.Filter = "Excel files (*.xlsx)|*.xlsx|All files (*.*)|*.*";
|
||
|
||
if (sDialog.ShowDialog() == DialogResult.OK)
|
||
{
|
||
string filename = sDialog.FileName;
|
||
|
||
try
|
||
{
|
||
//csExcelExport.ExportToExcelExt(BmsHistoryTable, filename);
|
||
DataTable[] export = new DataTable[1];
|
||
export[0] = BmsHistoryTable;
|
||
csExcelFunction.ExportDgvWithClosedXml(filename, export);
|
||
|
||
MessageBox.Show("Complete Export Excel File", "Result", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show(ex.Message, "Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
}
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region GRID DISPLAY
|
||
private void gvBmsHistory_CustomColumnDisplayText(object sender, DevExpress.XtraGrid.Views.Base.CustomColumnDisplayTextEventArgs e)
|
||
{
|
||
if (e.Column.FieldName == "status")
|
||
{
|
||
int status = Convert.ToInt32(e.Value);
|
||
switch (status)
|
||
{
|
||
case 0:
|
||
e.DisplayText = "Standby";
|
||
break;
|
||
case 1:
|
||
e.DisplayText = "Charging";
|
||
break;
|
||
case 2:
|
||
e.DisplayText = "Discharging";
|
||
break;
|
||
case 4:
|
||
e.DisplayText = "Protect";
|
||
break;
|
||
case 8:
|
||
e.DisplayText = "Charging-Lmt";
|
||
break;
|
||
case 0x32:
|
||
e.DisplayText = "Power-On";
|
||
break;
|
||
default:
|
||
e.DisplayText = string.Format("Unknown({0:X4})", status);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region TIMER EVENT
|
||
private void TmrCheckReq_Tick(object sender, EventArgs e)
|
||
{
|
||
DateTime nDateTime = ReqDateTime.AddSeconds(1);
|
||
|
||
if (DateTime.Now - ReqDateTime > TimeSpan.FromSeconds(1))
|
||
{
|
||
req_timeoutCount++;
|
||
|
||
if (req_timeoutCount >= 3)
|
||
{
|
||
StopAndFinish("Read Finished");
|
||
}
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region FORM EVENT
|
||
private void fmxHistory_Load(object sender, EventArgs e)
|
||
{
|
||
//SetGridColumnFormat();
|
||
}
|
||
#endregion
|
||
}
|
||
}
|