From 56e341a48a66a3d258ff29f7b11973e237488571 Mon Sep 17 00:00:00 2001 From: jkwoo Date: Sat, 20 Dec 2025 12:24:39 +0900 Subject: [PATCH] V1.0.1.8 -- 2025/12/20 * BMS History Function Improved --- LFP_Manager/Controls/ucHistroy.cs | 6 +- LFP_Manager/Forms/fmxHistory.Designer.cs | 25 +- LFP_Manager/Forms/fmxHistory.cs | 340 +++++++++++++++++---- LFP_Manager/Forms/fmxHistory.resx | 44 +-- LFP_Manager/Forms/fmxMain.cs | 4 +- LFP_Manager/Function/csExcelFunction.cs | 215 ++++++++++++- LFP_Manager/LFP_Manager_DBG_Delta.csproj | 25 ++ LFP_Manager/Properties/AssemblyInfo.cs | 4 +- LFP_Manager/Threads/CsRs232Thread124050.cs | 29 +- LFP_Manager/Threads/CsRs485Thread124050.cs | 49 +-- LFP_Manager/Utils/csUtils.cs | 23 ++ LFP_Manager/packages.config | 8 + 12 files changed, 651 insertions(+), 121 deletions(-) diff --git a/LFP_Manager/Controls/ucHistroy.cs b/LFP_Manager/Controls/ucHistroy.cs index e2edaba..0582eb9 100644 --- a/LFP_Manager/Controls/ucHistroy.cs +++ b/LFP_Manager/Controls/ucHistroy.cs @@ -180,7 +180,7 @@ namespace LFP_Manager.Controls SaveFileDialog sDialog = new SaveFileDialog(); sDialog.Title = "Select save file"; sDialog.DefaultExt = "xlsx"; - sDialog.Filter = "Excel files 2003 (*.xls)|*.xls|All files (*.*)|*.*"; + sDialog.Filter = "Excel files (*.xlsx)|*.xlsx|All files (*.*)|*.*"; if (sDialog.ShowDialog() == DialogResult.OK) { @@ -188,7 +188,9 @@ namespace LFP_Manager.Controls try { - csExcelExport.ExportToExcelExt(dtHistory, filename); + DataTable[] export = new DataTable[1]; + export[0] = dtHistory; + csExcelFunction.ExportDgvWithClosedXml(filename, export); MessageBox.Show("Complete Export Excel File", "Result", MessageBoxButtons.OK, MessageBoxIcon.Information); } diff --git a/LFP_Manager/Forms/fmxHistory.Designer.cs b/LFP_Manager/Forms/fmxHistory.Designer.cs index 59f8009..6116120 100644 --- a/LFP_Manager/Forms/fmxHistory.Designer.cs +++ b/LFP_Manager/Forms/fmxHistory.Designer.cs @@ -79,7 +79,7 @@ this.layoutControlGroup3 = new DevExpress.XtraLayout.LayoutControlGroup(); this.emptySpaceItem1 = new DevExpress.XtraLayout.EmptySpaceItem(); this.layoutControlItem5 = new DevExpress.XtraLayout.LayoutControlItem(); - this.layoutControlGroup4 = new DevExpress.XtraLayout.LayoutControlGroup(); + this.LcGrResult = new DevExpress.XtraLayout.LayoutControlGroup(); this.layoutControlItem4 = new DevExpress.XtraLayout.LayoutControlItem(); this.layoutControlItem6 = new DevExpress.XtraLayout.LayoutControlItem(); this.emptySpaceItem2 = new DevExpress.XtraLayout.EmptySpaceItem(); @@ -106,7 +106,7 @@ ((System.ComponentModel.ISupportInitialize)(this.layoutControlGroup3)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.emptySpaceItem1)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.layoutControlItem5)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.layoutControlGroup4)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.LcGrResult)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.layoutControlItem4)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.layoutControlItem6)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.emptySpaceItem2)).BeginInit(); @@ -712,7 +712,7 @@ this.layoutControlGroup2.GroupBordersVisible = false; this.layoutControlGroup2.Items.AddRange(new DevExpress.XtraLayout.BaseLayoutItem[] { this.layoutControlGroup3, - this.layoutControlGroup4}); + this.LcGrResult}); this.layoutControlGroup2.Name = "Root"; this.layoutControlGroup2.Padding = new DevExpress.XtraLayout.Utils.Padding(2, 2, 2, 2); this.layoutControlGroup2.Size = new System.Drawing.Size(903, 526); @@ -748,17 +748,17 @@ this.layoutControlItem5.TextSize = new System.Drawing.Size(0, 0); this.layoutControlItem5.TextVisible = false; // - // layoutControlGroup4 + // LcGrResult // - this.layoutControlGroup4.Items.AddRange(new DevExpress.XtraLayout.BaseLayoutItem[] { + this.LcGrResult.Items.AddRange(new DevExpress.XtraLayout.BaseLayoutItem[] { this.layoutControlItem4, this.layoutControlItem6, this.emptySpaceItem2}); - this.layoutControlGroup4.Location = new System.Drawing.Point(0, 64); - this.layoutControlGroup4.Name = "layoutControlGroup4"; - this.layoutControlGroup4.Padding = new DevExpress.XtraLayout.Utils.Padding(1, 1, 1, 1); - this.layoutControlGroup4.Size = new System.Drawing.Size(899, 458); - this.layoutControlGroup4.Text = "Result"; + this.LcGrResult.Location = new System.Drawing.Point(0, 64); + this.LcGrResult.Name = "LcGrResult"; + this.LcGrResult.Padding = new DevExpress.XtraLayout.Utils.Padding(1, 1, 1, 1); + this.LcGrResult.Size = new System.Drawing.Size(899, 458); + this.LcGrResult.Text = "Result"; // // layoutControlItem4 // @@ -850,6 +850,7 @@ this.Name = "fmxHistory"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "History"; + this.Load += new System.EventHandler(this.fmxHistory_Load); ((System.ComponentModel.ISupportInitialize)(this.layoutControl1)).EndInit(); this.layoutControl1.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.tabHistory)).EndInit(); @@ -868,7 +869,7 @@ ((System.ComponentModel.ISupportInitialize)(this.layoutControlGroup3)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.emptySpaceItem1)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.layoutControlItem5)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.layoutControlGroup4)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.LcGrResult)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.layoutControlItem4)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.layoutControlItem6)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.emptySpaceItem2)).EndInit(); @@ -902,7 +903,7 @@ private DevExpress.XtraLayout.LayoutControlItem layoutControlItem5; private DevExpress.XtraLayout.EmptySpaceItem emptySpaceItem1; private DevExpress.XtraLayout.LayoutControlGroup layoutControlGroup3; - private DevExpress.XtraLayout.LayoutControlGroup layoutControlGroup4; + private DevExpress.XtraLayout.LayoutControlGroup LcGrResult; private DevExpress.XtraGrid.Columns.GridColumn gcHistDateTime; private DevExpress.XtraGrid.Columns.GridColumn gcStatus; private DevExpress.XtraGrid.Columns.GridColumn gcWarning; diff --git a/LFP_Manager/Forms/fmxHistory.cs b/LFP_Manager/Forms/fmxHistory.cs index b6d8f8a..dfa7e97 100644 --- a/LFP_Manager/Forms/fmxHistory.cs +++ b/LFP_Manager/Forms/fmxHistory.cs @@ -1,5 +1,13 @@ -using LFP_Manager.DataStructure; +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; @@ -15,9 +23,16 @@ namespace LFP_Manager.Forms 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; @@ -28,21 +43,34 @@ namespace LFP_Manager.Forms #region CONTRUCTORS - //public fmxHistory() - //{ - // InitializeComponent(); - //} - 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; - CreateHistoryColomn(ref BmsHistoryTable); + CreateHistoryColumn(ref BmsHistoryTable); + BuildHistoryGridColumns(gcBmsHistory, gvBmsHistory, BmsHistoryTable); + + gvBmsHistory.Appearance.HeaderPanel.TextOptions.HAlignment = HorzAlignment.Center; + gvBmsHistory.Appearance.HeaderPanel.TextOptions.VAlignment = VertAlignment.Center; switch (Config.CommType) { @@ -67,7 +95,7 @@ namespace LFP_Manager.Forms #region INITIALIZE COMPONENT - private void CreateHistoryColomn(ref DataTable hDT) + private void CreateHistoryColumn(ref DataTable hDT) { hDT = new DataTable(); @@ -78,52 +106,223 @@ namespace LFP_Manager.Forms hDT.Columns.Add("protect", typeof(string)); hDT.Columns.Add("error", typeof(string)); - hDT.Columns.Add("voltage", typeof(float)); - hDT.Columns.Add("current", typeof(float)); - hDT.Columns.Add("SOC", typeof(float)); - hDT.Columns.Add("SOH", typeof(float)); + hDT.Columns.Add("voltage", typeof(decimal)); + hDT.Columns.Add("current", typeof(decimal)); + hDT.Columns.Add("SOC", typeof(decimal)); - for (int i = 0; i < 6; i++) + 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(float)); + hDT.Columns.Add(string.Format("temp_{0:00}", i + 1), typeof(decimal)); } - for (int i = 0; i < 16; i++) + for (int i = 0; i < cell_qty; i++) { - hDT.Columns.Add(string.Format("cell_v_{0:00}", i + 1), typeof(float)); + 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 - private short[] ByteArrTo2Short(byte[] bytes) + public void RecvUartData(byte[] data) { - short[] result = null; - - if (bytes != null) + if (InvokeRequired) { - int len = bytes[2]; - result = new short[len / 2]; - - for (int i = 0; i < result.Length; i++) - { - result[i] = (short)((bytes[3 + (i * 2) + 0] << 8) + bytes[3 + (i * 2) + 1]); - } + BeginInvoke(new Action(() => RecvUartData(data))); + return; } - return result; - } - public void RecvUardData(byte[] data) - { + rData = data; - if ((rData != null) && (rData.Length > 58)) + LcGrResult.Text = $"Result ({rData.Length})"; + + if ((rData != null) && (rData.Length == recv_data_len)) { try { + req_timeoutCount = 0; + DataRow hRow = BmsHistoryTable.NewRow(); - short[] shorts = ByteArrTo2Short(data); + short[] shorts = csUtils.ByteArrTo2ShortSafe(data); int pos = 0; int yy, MM, dd, HH, mm, ss; @@ -146,7 +345,13 @@ namespace LFP_Manager.Forms , ss ); - DateTime aDateTime = Convert.ToDateTime(stDateTime); + 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; @@ -154,22 +359,26 @@ namespace LFP_Manager.Forms 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"] = string.Format("{0:0.00}", Convert.ToDouble(shorts[pos++]) / 100); - hRow["current"] = string.Format("{0:0.00}", Convert.ToDouble(shorts[pos++]) / 100); - hRow["SOC"] = string.Format("{0:0.0}", Convert.ToDouble(shorts[pos++]) / 1); + 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 < 3; i++) + 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 < 16; i++) + for (int i = 0; i < cell_qty; i++) { - hRow[string.Format("cell_v_{0:00}", i + 1)] = Convert.ToDouble(shorts[pos++]) / 1000; + hRow[string.Format("cell_v_{0:00}", i + 1)] = Convert.ToDecimal(shorts[pos++]) / 1000m; } - hRow["SOH"] = string.Format("{0:0.0}", Convert.ToDouble(shorts[pos++]) / 1); + //hRow["SOH"] = shorts[pos++] / 1m; BmsHistoryTable.Rows.Add(hRow); } @@ -178,8 +387,6 @@ namespace LFP_Manager.Forms MessageBox.Show(e.Message); } } - gvBmsHistory.BestFitColumns(); - gvBmsHistory.ClearSelection(); hist_no++; curr_addr += 0x20; @@ -212,9 +419,7 @@ namespace LFP_Manager.Forms } else { - stopReq = true; - //btnBmsHistoryReq.Text = "Request"; - //TmrCheckReq.Enabled = false; + StopAndFinish("User Stop Request"); } } @@ -230,7 +435,7 @@ namespace LFP_Manager.Forms sdata[2] = (byte)(addr >> 8); // Address H sdata[3] = (byte)(addr >> 0); // Address L sdata[4] = 0x00; // Number of Register H - sdata[5] = 0x1D; // Number of Register L + sdata[5] = 0x35; // Number of Register L crc = csSerialCommFunction.GetCRC(sdata, 6); @@ -244,12 +449,29 @@ namespace LFP_Manager.Forms } } + 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 2003 (*.xls)|*.xls|All files (*.*)|*.*"; + sDialog.Filter = "Excel files (*.xlsx)|*.xlsx|All files (*.*)|*.*"; if (sDialog.ShowDialog() == DialogResult.OK) { @@ -257,7 +479,10 @@ namespace LFP_Manager.Forms try { - csExcelExport.ExportToExcelExt(BmsHistoryTable, filename); + //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); } @@ -308,14 +533,23 @@ namespace LFP_Manager.Forms { DateTime nDateTime = ReqDateTime.AddSeconds(1); - if (nDateTime < DateTime.Now) + if (DateTime.Now - ReqDateTime > TimeSpan.FromSeconds(1)) { - TmrCheckReq.Enabled = false; + req_timeoutCount++; - MessageBox.Show("Finished BMS History Read", "Result", MessageBoxButtons.OK, MessageBoxIcon.Information); - btnBmsHistoryReq.Text = "Request"; + if (req_timeoutCount >= 3) + { + StopAndFinish("Read Finished"); + } } } #endregion + + #region FORM EVENT + private void fmxHistory_Load(object sender, EventArgs e) + { + //SetGridColumnFormat(); + } + #endregion } } diff --git a/LFP_Manager/Forms/fmxHistory.resx b/LFP_Manager/Forms/fmxHistory.resx index 2e52af4..6211277 100644 --- a/LFP_Manager/Forms/fmxHistory.resx +++ b/LFP_Manager/Forms/fmxHistory.resx @@ -124,28 +124,28 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - vAAADrwBlbxySQAABMxJREFUWEflVWtsFVUQPrdtSvilMRIVY8T4h1/aoDGKqIWCIBXR+IgaAmriD/CR - SIyVYjSSPmhLrVVTBa6tQNGIBSqYCEEera34KCqFEmgLNa34qPRaeqHv23G+2Z3tXrp3u/iHGCeZnN09 - M2e++WbOrKFLLP8dACMjI87qp2oTVAIBcB8Y8zncvRcUxEUxoIdGon3UdbbX0TPdvdR9vl/23HZBZFwA - euDQcEzedze00aSF79Ckx9+nKx8rc3Tyo2V0+GSn2AyzbVAgvgD0gFgsRsOsfQNDNPXZCjLpq8nc9xaZ - ucWW4vmuPJqZ9Rm8xDZIcEggBjT7VR8fJHNnLiUveNsKOr/EUn5Ovp+/MYhNe5vEFj5BWEgIQJ1BJ5qr - 5dcITcwsoZBmzIFDeBcA9jq7iK554gOK9PQJC8KEfVYiGbcEmv0Db1aTuTufkjKt7CXgvWu4BKw2C0lg - YUYevbTugPgM2iz4iScAzX5waFjePz/YSmZWgUWzBuJSLF+7nx7J3SlB5Rv2GMwEXrUhxyvFGABqiMaD - 87neAbrxqTCF5hRx9hbVoTlr6CrufDRly+kIpc4rpiQO6oCbWUDpWVvsJPxZGAsAyg5KffZHdXGNJ+s9 - +bRhz1HZh7xa8bU0oGMDEPxeufeY7PuxEAdAjfQeH2/vsrJj9coOB6PRengITVmyXpjRxgzZDfk3Dy3Y - gFGN4RbPEgAAZP7r26zGc+rL2fEt+Kn1TweA9smWmuNim8pDCgxgxfsKZgeiLFwoDgBs6qGQ7fUtdtcn - 6HAOjMxgj17AikFk0t7gkuWRmZ5L5o4cMrfnUE1jB9uOMusGIgD0g0w8NoqeH6Api9eTySi06LQpvW7R - Ojp7rl8Cs5P4uAX/hWwGmF1RJ5mv3FBHr5Tto+pvWmXfi4U4BpTOrPLa+KuFldn4lGmGACSGTUPzH7Tn - 0C9UsfsIlWxtoPCXjfTF96dox7etcnWhO787SZ981UT1TactXwbvZsHoi9JztO0vuceotwTH4OGun51d - JVmrHcB2MxsdnT104HA7vcYZX79orVUCph2ApRQzuBTTc+jyh96l385ExzQkA7AecDAkY0UVB1w9mj0r - bsKRtk4ZyQrAS/C9fFcjXc1/RsNzI3VhKaXwORMetBpycfEusXOXgksw2nib9x3j+5vvTDy511wK3HMI - slZHrKrICnua2YmOCE19+kOrh7SJsc4qpP0/t4uNgjB4wAGo6bVPMoUZRaONx1ncsCRMUZ6GsIFCFARE - QejzwOCQPDfzz+syvopJ87SUDCC9gG5etpFt+AZxXAA2vf2Ww3LuXtTMoR4OXPtt9c2yr9m7g7tF9uy1 - 3wZRWv3j2Gbm95Lth2Qf11duQeOpTmm8FM4agVMWlErN5q7cKoZe18dL1AaZwQd609KNMj1RTpydzP10 - xcPv0e9dUbbjEuBeT3u+0upcrhmMETyFJ96Jjq64xgsKAmqBJqrlISQ3gumXs3memNtWUSZPWYjJCtdS - 2nObaNoLlQLk1hc3Uxo3UGHVD2Lg1/V+Ah/tmZfDNZT2TLmcjRi3sCLmDvzmUYdEguw1+MWAcPv4+aH/ - nFGMYKryzvVR8TskkaiPnuUVA+JMQi+F6Ppv5MLzvNT5F1wq+b8DIPoHjJf5qaq9hPwAAAAASUVORK5C - YII= + vAAADrwBlbxySQAABNdJREFUWEftVWtsFFUUvn2kxF8aI/EVI8Y//NIGjUFaaKGtfYHVKEQNATHxh4om + EmOlGIyk26XdbretptpSugJFI5RSwUQIAn2Kj6KyUFLaQk0XfFS6lm4fu+1OP3POzJ3OdrfT0R/4x5Pc + 3Jm5597zne98547Af2xi9oebbZYBTE9P67PZMPpaMUsAjAcqJocb16yCsASAzJihzz+BoRvj+rg+PI7h + sUCEnxWbF4A8cCqk8Pvxzn4szKvEwuc+wh3rqvRxz9oqnLs8yD6hkGIZiCkAeYCiKAgpCiaCU1j8shsi + dSdEdhlEplMd9Ly8CCvzD9Iu9rUSnMwUAJkx+x2fnoFIsiFuTbkaNMeljuwyxK0uZxD7TnaxL+2xwsKc + AORmopPE1XvVh1tyXYiRGee4EEPvDECb0x24+/mP4RuZYBaYiXkEOScAMmP2T77fBLHCjthcNXsO+EQp + RGapzkIssZBchDdrmnnPpMaCmUUFILOfnArx+xdn+iBWFas0y0BJNmypPo1nbUc5KH+jtRwXFmSX6YKc + rxQRAIzCo82j40E8+GItYjIciNUoj8koxZ3rqliUvdd8SMhyIpbKIsGtLEZq/gEtCXMWIgHMor7gk/Yw + 4fGcYseeExf0Pe+421iAuo8myPqTF3ndjIUwAEbh0dw9MKRml6UKb3Z2dDAJbWQsgEUbdzEzUpgxmiD/ + 8quCJEZlDKNFMqABIMvZ3qgKT69vGeIynfip7w8dgNTJgZZu9k3Iq2QGaKb3re42XpcszDYdgMxeUn+4 + o1dT/RwKnwpxZuRPWqCZLiKR+B5EUhHEMhvE44UQSwvR4vEipMwwawTCAOQHvvFCCvxjQSzasAsirUSl + U6P0vvU1uDEa4MCIkg39FwpqmlHgbufMt+1px9tVp9D0TR+vR2MhjAFJZ35da3hr0bzCjs9bunmdQNJl + 09nzO06c/QXu4+fhOtSJ2q88+PL7KzjybR+3Lo2j313GZ193oaPrmrpXu6YlECFfJD0X+v/kPqZ6c3C6 + eFLsSC9o4KylH4EdHg3AOziC5nMDeNfdhvvXV6slWFrIgLkUyTaIZYW47ekP8Ot1f4QgBQGRAMjStjZA + pOycyT67jDvhfP8gX8kSQDSj73XHPLhrbRVEhgMJeRWIX12OBU+pgtzgPMZ+xlII6nwpvP2nLkIst+s3 + Hvd1chH3ORllLTdK5hi8onaDzOyS14fFm3arGpIipnlVCU7/PBAGQsheppre+0I1RJpjRngZDjywsRb+ + 8aD+c5HBpUkQ8jk4OcXPPVd9uDWvErFZspQuiNRiPPzqXgQnQ+pPTlEgxgPqhi01zVwznXrakGJHY0cP + r8vszeiXt2hAA1HR9GOkmJOL4Dp8ltepfbkLPFcGWXjxOS4OHL+mgmuWue0QO0Zrn2gmfeR/hMZDr+zl + 25PKSWfHZTlx+zMf4rchPxRlGoL6esnmelW5aSXsTMHjM5245B0KE55VEDRU0ECrx6t2RGqxena6A+Kx + Hcjd3sj+Ir+2FYmv7cOS1+sZyKNv7Efipt0oafiBHcxUb2a0R2rmrdoWJL5Ux2dTjEc213PMI/SbpzrM + ZZS9UWBWzbjHbB/pT7+KKZgc/K6EK/2fmhEEnRUtBpl+E0YbxoP+jc0+L9qI+B3fbPsfwN+Ml/mpJunQ + YAAAAABJRU5ErkJggg== \ No newline at end of file diff --git a/LFP_Manager/Forms/fmxMain.cs b/LFP_Manager/Forms/fmxMain.cs index fac204e..3f7e537 100644 --- a/LFP_Manager/Forms/fmxMain.cs +++ b/LFP_Manager/Forms/fmxMain.cs @@ -274,13 +274,13 @@ namespace LFP_Manager.Forms this.Invoke(new MethodInvoker(delegate () { FwUpdateForm?.RecvData(data); - HistoryForm?.RecvUardData(data); + HistoryForm?.RecvUartData(data); })); } else { FwUpdateForm?.RecvData(data); - HistoryForm?.RecvUardData(data); + HistoryForm?.RecvUartData(data); } } diff --git a/LFP_Manager/Function/csExcelFunction.cs b/LFP_Manager/Function/csExcelFunction.cs index 3f10aa2..4363a36 100644 --- a/LFP_Manager/Function/csExcelFunction.cs +++ b/LFP_Manager/Function/csExcelFunction.cs @@ -1,16 +1,19 @@ -using System; +using ClosedXML.Excel; +using System; using System.Collections.Generic; +using System.Data; +using System.Data.OleDb; +using System.Globalization; +using System.IO; using System.Linq; using System.Text; -using System.Data; using System.Windows.Forms; -using System.Data.OleDb; - namespace LFP_Manager.Function { public class csExcelFunction { + #region EXCEL IMPORT / EXPORT public static DataTable[] ExcelImport(string Ps_FileName, string[] sheetName) { DataTable[] result = null; @@ -141,5 +144,209 @@ namespace LFP_Manager.Function connection.Dispose(); } } + #endregion + + #region EXCEL CLOSED XML IMPORT / EXPORT + + private static readonly string[] IsoDateFormats = new string[] + { + "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-ddTHH:mm:ss", + "yyyy-MM-ddTHH:mm:ss.fff", + "yyyy-MM-ddTHH:mm:ssZ", + "yyyy-MM-ddTHH:mm:ss.fffZ", + "yyyy-MM-dd" + }; + + public static bool ExportDgvWithClosedXml(string filePath, DataTable[] dt, bool overwiteFile = true) + { + if (dt == null || dt.Length == 0) return false; + + // .xls로 들어오면 .xlsx로 변환하여 저장 경로 결정 + var outputPath = EnsureXlsxPath(filePath); + + // 저장 경로 폴더 보장 + var dir = Path.GetDirectoryName(outputPath); + if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) + Directory.CreateDirectory(dir); + + if (File.Exists(outputPath)) + { + if (overwiteFile) File.Delete(outputPath); + else throw new IOException("File already exists: " + outputPath); + } + + try + { + using (var wb = new XLWorkbook()) // C# 7.3 호환 + { + var nameCount = new Dictionary(StringComparer.OrdinalIgnoreCase); + + for (int i = 0; i < dt.Length; i++) + { + var table = dt[i]; + if (table == null) continue; + + string baseName = !string.IsNullOrWhiteSpace(table.TableName) ? table.TableName : ("Sheet" + (i + 1)); + string sheetName = SanitizeSheetName(baseName); + + // 중복 시트명 처리: _2, _3... + int dup; + if (nameCount.TryGetValue(sheetName, out dup)) + { + dup++; + nameCount[sheetName] = dup; + string prefix = sheetName.Length > 28 ? sheetName.Substring(0, 28) : sheetName; // "_n" 자리 고려 + sheetName = prefix + "_" + dup; + } + else + { + nameCount[sheetName] = 1; + } + + // DataTable 전체를 시트로 추가 (헤더 포함) + var ws = wb.Worksheets.Add(table, sheetName); + + // 자동 필터 + var tbl = ws.Tables.FirstOrDefault(); + if (tbl != null) tbl.ShowAutoFilter = true; + + // ====== [추가] 문자열 날짜 → 진짜 DateTime으로 변환 ====== + // 1) 날짜 컬럼 후보 탐지 (타입/컬럼명/샘플링) + bool[] isDateCol = new bool[table.Columns.Count]; + for (int c = 0; c < table.Columns.Count; c++) + { + var colType = table.Columns[c].DataType; + if (colType == typeof(DateTime)) { isDateCol[c] = true; continue; } + + if (colType == typeof(string)) + { + string colName = table.Columns[c].ColumnName ?? ""; + // 컬럼명 힌트 + if (colName.IndexOf("date", StringComparison.OrdinalIgnoreCase) >= 0 || + colName.IndexOf("time", StringComparison.OrdinalIgnoreCase) >= 0 || + colName.EndsWith("_at", StringComparison.OrdinalIgnoreCase)) + { + isDateCol[c] = true; + continue; + } + + // 값 샘플링 (최대 20개) + int seen = 0, hits = 0; + for (int r = 0; r < table.Rows.Count && seen < 20; r++) + { + var obj = table.Rows[r][c]; + if (obj == null || obj == DBNull.Value) continue; + var s = obj.ToString(); + if (string.IsNullOrWhiteSpace(s)) continue; + seen++; + DateTime tmp; + if (TryParseIsoDate(s, out tmp)) hits++; + } + if (seen > 0 && (hits * 1.0 / seen) >= 0.6) isDateCol[c] = true; + } + } + + // 2) 열 서식 지정 + 각 셀 값 DateTime으로 재설정 + for (int c = 0; c < table.Columns.Count; c++) + { + var col = ws.Column(c + 1); + Type t = table.Columns[c].DataType; + + if (isDateCol[c]) + { + // 열 서식(표시): 날짜/시간 + col.Style.DateFormat.Format = "yyyy-MM-dd HH:mm:ss"; + + // 데이터 행(2행부터) 변환 + int rowCount = table.Rows.Count; + for (int r = 0; r < rowCount; r++) + { + var cell = ws.Cell(r + 2, c + 1); + + // 이미 DateTime 셀은 스킵 + if (cell.DataType == ClosedXML.Excel.XLDataType.DateTime) + continue; + + var s = cell.GetString(); + if (string.IsNullOrWhiteSpace(s)) continue; + + DateTime dtv; + if (TryParseIsoDate(s, out dtv)) + { + cell.Value = dtv; // ✅ 진짜 날짜로 저장 (엑셀 DateTime) + // cell.Style.DateFormat.Format = "yyyy-MM-dd HH:mm:ss"; // per-cell 필요시 + } + } + } + else + { + // 기존 숫자/텍스트 서식 유지 + if (t == typeof(DateTime)) + col.Style.DateFormat.Format = "yyyy-MM-dd HH:mm:ss"; + else if (t == typeof(int) || t == typeof(long) || t == typeof(short) || t == typeof(byte)) + col.Style.NumberFormat.Format = "0"; // 정수 + else if (t == typeof(decimal) || t == typeof(float) || t == typeof(double)) + col.Style.NumberFormat.Format = "General"; // 일반 숫자 + else + col.Style.NumberFormat.Format = "@"; // 텍스트 + } + } + // ====== [추가 끝] ====== + + // 열 너비 자동 조정 + ws.Columns().AdjustToContents(); + } + wb.SaveAs(outputPath); + } + } + catch (Exception ex) + { + // 필요 시 로깅 추가 + throw new Exception(ex.Message); + } + return true; + } + + private static string EnsureXlsxPath(string filePath) + { + if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException(nameof(filePath)); + var ext = Path.GetExtension(filePath); + if (!string.IsNullOrEmpty(ext) && ext.Equals(".xls", StringComparison.OrdinalIgnoreCase)) + return Path.ChangeExtension(filePath, ".xlsx"); + return filePath; + } + + private static string SanitizeSheetName(string name) + { + if (string.IsNullOrEmpty(name)) return "Sheet1"; + // Excel 금지문자: : \ / ? * [ ] + char[] invalid = { ':', '\\', '/', '?', '*', '[', ']' }; + var arr = name.ToCharArray(); + for (int i = 0; i < arr.Length; i++) + for (int j = 0; j < invalid.Length; j++) + if (arr[i] == invalid[j]) { arr[i] = '_'; break; } + + var sanitized = new string(arr); + if (sanitized.Length > 31) sanitized = sanitized.Substring(0, 31); + if (string.IsNullOrWhiteSpace(sanitized)) sanitized = "Sheet1"; + return sanitized; + } + + private static bool TryParseIsoDate(string s, out DateTime dt) + { + // 로컬 시간 가정(오프셋 보정 없음). DB 문자열이 UTC라면 AdjustToUniversal로 변경하세요. + return DateTime.TryParseExact( + s, IsoDateFormats, + CultureInfo.InvariantCulture, + DateTimeStyles.AssumeLocal, + out dt) + || DateTime.TryParse( + s, CultureInfo.InvariantCulture, + DateTimeStyles.AssumeLocal, + out dt); + } + + #endregion } } diff --git a/LFP_Manager/LFP_Manager_DBG_Delta.csproj b/LFP_Manager/LFP_Manager_DBG_Delta.csproj index 850bf56..bbc8888 100644 --- a/LFP_Manager/LFP_Manager_DBG_Delta.csproj +++ b/LFP_Manager/LFP_Manager_DBG_Delta.csproj @@ -51,6 +51,12 @@ + + ..\packages\ClosedXML.0.105.0\lib\netstandard2.0\ClosedXML.dll + + + ..\packages\ClosedXML.Parser.2.0.0\lib\netstandard2.0\ClosedXML.Parser.dll + True @@ -119,12 +125,21 @@ + + ..\packages\DocumentFormat.OpenXml.3.1.1\lib\net46\DocumentFormat.OpenXml.dll + + + ..\packages\DocumentFormat.OpenXml.Framework.3.1.1\lib\net46\DocumentFormat.OpenXml.Framework.dll + ..\packages\EntityFramework.6.5.1\lib\net45\EntityFramework.dll ..\packages\EntityFramework.6.5.1\lib\net45\EntityFramework.SqlServer.dll + + ..\packages\ExcelNumberFormat.1.1.0\lib\net20\ExcelNumberFormat.dll + dll\IPAddressControlLib.dll @@ -134,6 +149,9 @@ ..\packages\Microsoft.Bcl.AsyncInterfaces.9.0.5\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll + + ..\packages\Microsoft.Bcl.HashCode.1.1.1\lib\net461\Microsoft.Bcl.HashCode.dll + ..\packages\Microsoft.Extensions.Configuration.9.0.5\lib\net462\Microsoft.Extensions.Configuration.dll @@ -173,6 +191,12 @@ ..\packages\Microsoft.Extensions.Primitives.9.0.5\lib\net462\Microsoft.Extensions.Primitives.dll + + ..\packages\RBush.Signed.4.0.0\lib\net47\RBush.dll + + + ..\packages\SixLabors.Fonts.1.0.0\lib\netstandard2.0\SixLabors.Fonts.dll + False dll\SnmpSharpNet.dll @@ -242,6 +266,7 @@ dll\Tftp.Net.dll + diff --git a/LFP_Manager/Properties/AssemblyInfo.cs b/LFP_Manager/Properties/AssemblyInfo.cs index f968bc7..3373eab 100644 --- a/LFP_Manager/Properties/AssemblyInfo.cs +++ b/LFP_Manager/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.1.7")] -[assembly: AssemblyFileVersion("1.0.1.7")] +[assembly: AssemblyVersion("1.0.1.8")] +[assembly: AssemblyFileVersion("1.0.1.8")] diff --git a/LFP_Manager/Threads/CsRs232Thread124050.cs b/LFP_Manager/Threads/CsRs232Thread124050.cs index 59c86ee..7bc236a 100644 --- a/LFP_Manager/Threads/CsRs232Thread124050.cs +++ b/LFP_Manager/Threads/CsRs232Thread124050.cs @@ -1,7 +1,8 @@ using System; using System.Threading; +using System.Threading.Tasks; using System.IO.Ports; -using System.Windows.Forms; + using LFP_Manager.DataStructure; using LFP_Manager.Function; using LFP_Manager.Utils; @@ -460,10 +461,11 @@ namespace LFP_Manager.Threads break; case 2: // Firmware update packet - if (OnPrint != null) - { - OnPrint(this, csLog.trx_data_print(_readBuffer, _readPosition, 1)); - } + SafeRaiseOnPrint(csLog.trx_data_print(_readBuffer, _readPosition, 1)); + //if (OnPrint != null) + //{ + // OnPrint(this, csLog.trx_data_print(_readBuffer, _readPosition, 1)); + //} _timeoutCount = 0; ProcessFirmwareResponse(); break; @@ -702,5 +704,22 @@ namespace LFP_Manager.Threads } #endregion + + #region SAFE CALLS + private void SafeRaiseOnPrint(string msg) + { + var handler = OnPrint; // 복사 + if (handler == null) return; + + // 비차단 + 예외격리: 핸들러를 ThreadPool에서 실행 + Task.Run(() => + { + try { handler.Invoke(this, msg); } + catch (Exception ex) { /* 내부 로깅 */ } + }); + } + + #endregion + } } \ No newline at end of file diff --git a/LFP_Manager/Threads/CsRs485Thread124050.cs b/LFP_Manager/Threads/CsRs485Thread124050.cs index 8fd58eb..4f791ce 100644 --- a/LFP_Manager/Threads/CsRs485Thread124050.cs +++ b/LFP_Manager/Threads/CsRs485Thread124050.cs @@ -34,11 +34,6 @@ namespace LFP_Manager.Threads private ushort ExtReqRegAddr = 0x0000; - private ushort WriteRegAddr = 0x0000; //Byul Init 0x0000 - private short WriteCoilRegData = 0; - private byte[] WriteRegData; - private short WriteParamRegData; - private TUartTxBuff UartTxBuff; public event UartDataUpdateRS485 OnUpdate = null; @@ -139,13 +134,13 @@ namespace LFP_Manager.Threads catch (Exception ex) { // 무시하지 말고 로그 출력 - OnPrint?.Invoke(this, $"DataRecv Error: {ex.Message}"); + SafeRaiseOnPrint($"DataRecv Error: {ex.Message}"); } } private void sErrorReceived(object sender, System.IO.Ports.SerialErrorReceivedEventArgs e) { - OnPrint?.Invoke(this, $"Serial Error: {e.EventType}"); + SafeRaiseOnPrint($"Serial Error: {e.EventType}"); } private void sPinChanged(object sender, System.IO.Ports.SerialPinChangedEventArgs e) @@ -186,7 +181,8 @@ namespace LFP_Manager.Threads } catch (Exception ex) { - OnPrint?.Invoke(this, $"Error Open - {ex.Message}"); + SafeRaiseOnPrint($"Error Open - {ex.Message}"); + if (sPort != null) { try { sPort.Dispose(); } catch { } @@ -220,7 +216,7 @@ namespace LFP_Manager.Threads } catch (Exception ex) { - OnPrint?.Invoke(this, $"Port Close Fail: {ex.Message}"); + SafeRaiseOnPrint($"Port Close Fail: {ex.Message}"); } } } @@ -245,8 +241,6 @@ namespace LFP_Manager.Threads public void SetWriteReg(ushort WriteAddr, byte[] WriteData, bool ReplyFlag, int type) { - WriteRegAddr = WriteAddr; - var uartTRxData = new TUartTRxData { type = type, @@ -448,7 +442,7 @@ namespace LFP_Manager.Threads { try { - OnPrint?.Invoke(this, $"uartCommThread Start"); + SafeRaiseOnPrint($"uartCommThread Start"); int RecvTimeout = (Config != null) ? Config.RecvWaitTime : 1500; int getData = 0; @@ -472,11 +466,11 @@ namespace LFP_Manager.Threads try { sPort.Write(txData, 0, txData.Length); - OnPrint?.Invoke(this, csLog.trx_data_print(txData, txData.Length, 0)); + SafeRaiseOnPrint(csLog.trx_data_print(txData, txData.Length, 0)); } catch (Exception ex) { - OnPrint?.Invoke(this, $"Write error: {ex.Message}"); + SafeRaiseOnPrint($"Write error: {ex.Message}"); } } @@ -519,7 +513,7 @@ namespace LFP_Manager.Threads case 0: // Need more data break; case 1: // Packet OK, no error - OnPrint?.Invoke(this, csLog.trx_data_print(ReadBuf, rPosition, 1)); + SafeRaiseOnPrint(csLog.trx_data_print(ReadBuf, rPosition, 1)); TimeOutCount[ModuleID - 1] = 0; @@ -535,7 +529,7 @@ namespace LFP_Manager.Threads Thread.Sleep(5); goto StartSend; case 2: // Fw Update Packet OK - OnPrint?.Invoke(this, csLog.trx_data_print(ReadBuf, rPosition, 1)); + SafeRaiseOnPrint(csLog.trx_data_print(ReadBuf, rPosition, 1)); TimeOutCount[ModuleID - 1] = 0; rFlag = false; @@ -564,7 +558,8 @@ namespace LFP_Manager.Threads if (rPosition > 0) { - OnPrint?.Invoke(this, csLog.trx_data_print(ReadBuf, rPosition, 1)); + SafeRaiseOnPrint(csLog.trx_data_print(ReadBuf, rPosition, 1)); + Thread.Sleep(1); } else @@ -595,15 +590,31 @@ namespace LFP_Manager.Threads catch (OperationCanceledException) { } catch (Exception ex) { - OnPrint?.Invoke(this, $"Comm thread error: {ex.Message}"); + SafeRaiseOnPrint($"Comm thread error: {ex.Message}"); } finally { SerialPortThreadEnd = true; - OnPrint?.Invoke(this, $"uartCommThread End"); + SafeRaiseOnPrint($"uartCommThread End"); } } #endregion + + #region SAFE CALLS + private void SafeRaiseOnPrint(string msg) + { + var handler = OnPrint; // 복사 + if (handler == null) return; + + // 비차단 + 예외격리: 핸들러를 ThreadPool에서 실행 + Task.Run(() => + { + try { handler.Invoke(this, msg); } + catch (Exception ex) { /* 내부 로깅 */ } + }); + } + + #endregion } } diff --git a/LFP_Manager/Utils/csUtils.cs b/LFP_Manager/Utils/csUtils.cs index d808732..afc51d4 100644 --- a/LFP_Manager/Utils/csUtils.cs +++ b/LFP_Manager/Utils/csUtils.cs @@ -208,6 +208,29 @@ namespace LFP_Manager.Utils byte[] StrByte = Encoding.UTF8.GetBytes(str); return StrByte; } + public static short[] ByteArrTo2ShortSafe(byte[] bytes) + { + if (bytes == null || bytes.Length < 5) return null; // addr+func+len+crc 최소 + int byteCount = bytes[2]; + + // byteCount가 짝수인지 확인(레지스터는 2바이트 단위) + if ((byteCount % 2) != 0) return null; + + // 데이터 영역 + CRC(2byte)까지 길이 확인 + int minLen = 3 + byteCount + 2; + if (bytes.Length < minLen) return null; + + int regCount = byteCount / 2; + var result = new short[regCount]; + + for (int i = 0; i < regCount; i++) + { + int idx = 3 + i * 2; + result[i] = (short)((bytes[idx] << 8) | bytes[idx + 1]); + } + return result; + } + #region OPERATING WARNING FUCTION public static bool BitCheck(short rData, int pos) diff --git a/LFP_Manager/packages.config b/LFP_Manager/packages.config index 510b29a..0778ace 100644 --- a/LFP_Manager/packages.config +++ b/LFP_Manager/packages.config @@ -1,7 +1,13 @@  + + + + + + @@ -15,6 +21,8 @@ + +