초기 커밋.
This commit is contained in:
469
LFP_Manager/Controls/ucMainStatus.cs
Normal file
469
LFP_Manager/Controls/ucMainStatus.cs
Normal file
@@ -0,0 +1,469 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using DevExpress.XtraCharts;
|
||||
using DevExpress.XtraLayout;
|
||||
using LFP_Manager.DataStructure;
|
||||
|
||||
namespace LFP_Manager.Controls
|
||||
{
|
||||
public delegate void CommandEvent(int sId, int cmd, int index, int flag,
|
||||
ref CsDeviceData.DeviceModuleData.DeviceParamData aParam,
|
||||
ref CsDeviceData.DeviceModuleData.DeviceCalibration aCalib);
|
||||
|
||||
public partial class ucMainStatus : DevExpress.XtraEditors.XtraUserControl
|
||||
{
|
||||
#region CONSTANTS / ENUMS
|
||||
|
||||
private const int HeaderHeight = 30;
|
||||
private const int RowHeight = 60;
|
||||
private const int BorderPadding = 2;
|
||||
|
||||
private static class Colors
|
||||
{
|
||||
public static readonly Color Normal = Color.Green;
|
||||
public static readonly Color Warning = Color.Orange;
|
||||
public static readonly Color Fault = Color.Red;
|
||||
public static readonly Color Offline = Color.Red;
|
||||
public static readonly Color Charging = Color.Blue;
|
||||
public static readonly Color Discharging = Color.Magenta;
|
||||
public static readonly Color Default = Color.Black;
|
||||
}
|
||||
|
||||
private enum OperatingStatus
|
||||
{
|
||||
Standby = 0,
|
||||
Charging = 1,
|
||||
Discharging = 2,
|
||||
Floating = 3
|
||||
}
|
||||
|
||||
private enum BatteryStatus
|
||||
{
|
||||
Normal = 0,
|
||||
Warning = 1,
|
||||
Fault = 2,
|
||||
WarmingUp = 3,
|
||||
FaultAntiTheftComm = 4,
|
||||
FaultAntiTheftGyro = 5
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private CommConfig _config;
|
||||
private CsDeviceData _deviceData;
|
||||
|
||||
private static CsDeviceData.DeviceModuleData dummy;
|
||||
|
||||
private ucModuleMainHeader _moduleMainHeader;
|
||||
private ucModuleMainB[] _moduleMainRows;
|
||||
|
||||
private LayoutControlItem[] _lcitemModuleMain;
|
||||
private EmptySpaceItem[] _emptySpaceItems;
|
||||
|
||||
private int _moduleQty;
|
||||
|
||||
private double _totalVoltage;
|
||||
private double _totalCurrent;
|
||||
private double _totalTemperature;
|
||||
private double _totalSoc;
|
||||
private double _totalSoh;
|
||||
|
||||
private bool _active;
|
||||
|
||||
public event CommandEvent OnCommand;
|
||||
|
||||
#endregion
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public ucMainStatus()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_moduleQty = csConstData.SystemInfo.MAX_MODULE_SIZE;
|
||||
|
||||
// 크기 변경 시 레이아웃 재배치
|
||||
this.Resize += (_, __) =>
|
||||
{
|
||||
if (_moduleMainRows != null && _moduleMainRows.Length > 0)
|
||||
RepositionLayout();
|
||||
};
|
||||
|
||||
// 🔧 리소스/이벤트 정리: Dispose 이벤트로 대체
|
||||
this.Disposed += UcMainStatus_Disposed;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PUBLIC API
|
||||
public void UpdateMainConfig(CommConfig config, CsDeviceData devData)
|
||||
{
|
||||
if (config == null || devData == null)
|
||||
return;
|
||||
|
||||
_config = config;
|
||||
_deviceData = devData;
|
||||
|
||||
_moduleQty = Math.Max(0, Math.Min(_config.ModuleQty, csConstData.SystemInfo.MAX_MODULE_SIZE));
|
||||
|
||||
LoadModuleMain(_moduleQty);
|
||||
lcgbModuleMain.Invalidate();
|
||||
}
|
||||
|
||||
public void Start(CommConfig config, CsDeviceData devData)
|
||||
{
|
||||
if (config == null || devData == null)
|
||||
return;
|
||||
|
||||
_config = config;
|
||||
_deviceData = devData;
|
||||
_moduleQty = Math.Max(0, Math.Min(_config.ModuleQty, csConstData.SystemInfo.MAX_MODULE_SIZE));
|
||||
|
||||
_active = true;
|
||||
tmrDisplay.Enabled = true;
|
||||
}
|
||||
public void Stop()
|
||||
{
|
||||
_active = false;
|
||||
tmrDisplay.Enabled = false;
|
||||
}
|
||||
|
||||
public void UpdateData(CsDeviceData devData)
|
||||
{
|
||||
if (!_active || devData == null)
|
||||
return;
|
||||
|
||||
_deviceData = devData;
|
||||
|
||||
// 합계 값 계산 (0으로 나눔 방지, 스케일 고정)
|
||||
_totalVoltage = _deviceData.TotalData?.ValueData != null ? _deviceData.TotalData.ValueData.TotalVoltage / 10.0 : 0.0;
|
||||
_totalCurrent = _deviceData.TotalData?.ValueData != null ? _deviceData.TotalData.ValueData.TotalCurrent / 10.0 : 0.0;
|
||||
_totalSoc = _deviceData.TotalData?.ValueData != null ? _deviceData.TotalData.ValueData.TotalSOC / 10.0 : 0.0;
|
||||
_totalSoh = _deviceData.TotalData?.ValueData != null ? _deviceData.TotalData.ValueData.TotalSOH / 10.0 : 0.0;
|
||||
_totalTemperature = _deviceData.TotalData?.ValueData != null ? _deviceData.TotalData.ValueData.TotalTemp / 10.0 : 0.0;
|
||||
|
||||
// 모듈 행 갱신 (범위/Null 안전)
|
||||
if (_moduleMainRows != null && _deviceData.ModuleData != null)
|
||||
{
|
||||
var count = Math.Min(Math.Min(_moduleMainRows.Length, _deviceData.ModuleData.Length), _moduleQty);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
_moduleMainRows[i]?.UpdateData(ref _deviceData.ModuleData[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UcMainStatus_Disposed(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 타이머 정지
|
||||
if (tmrDisplay != null)
|
||||
tmrDisplay.Enabled = false;
|
||||
|
||||
// 이벤트 구독 해제
|
||||
if (_moduleMainRows != null)
|
||||
{
|
||||
foreach (var row in _moduleMainRows)
|
||||
{
|
||||
if (row != null)
|
||||
row.OnCommand -= SetCmdEvent;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { /* swallow on dispose */ }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region INTERNAL LAYOUT
|
||||
|
||||
private void LoadModuleMain(int mQty)
|
||||
{
|
||||
if (lcModuleMain == null || lcgbModuleMain == null)
|
||||
return;
|
||||
|
||||
// 기존 컨트롤/아이템 제거 (성능 최적화)
|
||||
lcModuleMain.SuspendLayout();
|
||||
lcgbModuleMain.BeginUpdate();
|
||||
try
|
||||
{
|
||||
lcModuleMain.Controls.Clear();
|
||||
lcgbModuleMain.Items.Clear();
|
||||
|
||||
_moduleMainHeader = new ucModuleMainHeader();
|
||||
_moduleMainRows = new ucModuleMainB[mQty];
|
||||
|
||||
_lcitemModuleMain = new LayoutControlItem[mQty + 1];
|
||||
|
||||
var emptySize = csConstData.SystemInfo.MAX_MODULE_SIZE - mQty;
|
||||
_emptySpaceItems = emptySize > 0 ? new EmptySpaceItem[emptySize] : Array.Empty<EmptySpaceItem>();
|
||||
|
||||
// 헤더
|
||||
_lcitemModuleMain[0] = new LayoutControlItem
|
||||
{
|
||||
Control = _moduleMainHeader,
|
||||
Name = "lcitemModuleMainHeader",
|
||||
Size = new Size(Math.Max(0, lcgbModuleMain.Width - 2 * BorderPadding), HeaderHeight),
|
||||
Location = new Point(BorderPadding, BorderPadding),
|
||||
TextVisible = false,
|
||||
Padding = new DevExpress.XtraLayout.Utils.Padding(0)
|
||||
};
|
||||
|
||||
// 본문 행
|
||||
var tWidth = Math.Max(0, lcgbModuleMain.Width - 2 * BorderPadding);
|
||||
var y = BorderPadding + HeaderHeight;
|
||||
|
||||
var emptyIdx = 0;
|
||||
for (var i = 0; i < csConstData.SystemInfo.MAX_MODULE_SIZE; i++)
|
||||
{
|
||||
if (i < mQty)
|
||||
{
|
||||
// 기존 구독 제거(안전)
|
||||
if (_moduleMainRows[i] != null)
|
||||
_moduleMainRows[i].OnCommand -= SetCmdEvent;
|
||||
|
||||
var moduleDataRef = _deviceData != null && _deviceData.ModuleData != null && _deviceData.ModuleData.Length > i
|
||||
? ref _deviceData.ModuleData[i]
|
||||
: ref GetDummyModuleRef();
|
||||
|
||||
_moduleMainRows[i] = new ucModuleMainB(_config, i + 1, ref moduleDataRef);
|
||||
_moduleMainRows[i].OnCommand += SetCmdEvent;
|
||||
|
||||
_lcitemModuleMain[i + 1] = new LayoutControlItem
|
||||
{
|
||||
Control = _moduleMainRows[i],
|
||||
Name = $"lcitemModuleMain_{i + 1}",
|
||||
Size = new Size(tWidth, RowHeight),
|
||||
Location = new Point(BorderPadding, y),
|
||||
TextVisible = false,
|
||||
Padding = new DevExpress.XtraLayout.Utils.Padding(0)
|
||||
};
|
||||
|
||||
y += RowHeight;
|
||||
}
|
||||
else if (_emptySpaceItems.Length > 0 && emptyIdx < _emptySpaceItems.Length)
|
||||
{
|
||||
_emptySpaceItems[emptyIdx] = new EmptySpaceItem
|
||||
{
|
||||
Size = new Size(tWidth, RowHeight),
|
||||
Location = new Point(BorderPadding, y)
|
||||
};
|
||||
y += RowHeight;
|
||||
emptyIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
// 컨트롤 추가
|
||||
lcModuleMain.Controls.Add(_moduleMainHeader);
|
||||
for (var i = 0; i < mQty; i++)
|
||||
{
|
||||
if (_moduleMainRows[i] != null)
|
||||
lcModuleMain.Controls.Add(_moduleMainRows[i]);
|
||||
}
|
||||
|
||||
// 레이아웃 아이템 구성
|
||||
var items = new List<BaseLayoutItem>(csConstData.SystemInfo.MAX_MODULE_SIZE + 1);
|
||||
items.Add(_lcitemModuleMain[0]);
|
||||
for (var i = 1; i < _lcitemModuleMain.Length; i++)
|
||||
{
|
||||
if (_lcitemModuleMain[i] != null)
|
||||
items.Add(_lcitemModuleMain[i]);
|
||||
}
|
||||
if (_emptySpaceItems.Length > 0)
|
||||
items.AddRange(_emptySpaceItems);
|
||||
|
||||
lcgbModuleMain.Items.AddRange(items.ToArray());
|
||||
}
|
||||
finally
|
||||
{
|
||||
lcgbModuleMain.EndUpdate();
|
||||
lcModuleMain.ResumeLayout();
|
||||
lcgbModuleMain.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
// 크기 변경 시 아이템 사이즈/좌표만 갱신 (재생성 X)
|
||||
private void RepositionLayout()
|
||||
{
|
||||
if (_lcitemModuleMain == null || _lcitemModuleMain.Length == 0 || lcgbModuleMain == null)
|
||||
return;
|
||||
|
||||
lcgbModuleMain.BeginUpdate();
|
||||
try
|
||||
{
|
||||
var tWidth = Math.Max(0, lcgbModuleMain.Width - 2 * BorderPadding);
|
||||
|
||||
// 헤더
|
||||
if (_lcitemModuleMain[0] != null)
|
||||
{
|
||||
_lcitemModuleMain[0].Size = new Size(tWidth, HeaderHeight);
|
||||
_lcitemModuleMain[0].Location = new Point(BorderPadding, BorderPadding);
|
||||
}
|
||||
|
||||
// 행
|
||||
var y = BorderPadding + HeaderHeight;
|
||||
for (var i = 1; i < _lcitemModuleMain.Length; i++)
|
||||
{
|
||||
var item = _lcitemModuleMain[i];
|
||||
if (item == null) continue;
|
||||
|
||||
item.Size = new Size(tWidth, RowHeight);
|
||||
item.Location = new Point(BorderPadding, y);
|
||||
y += RowHeight;
|
||||
}
|
||||
|
||||
// 빈공간
|
||||
if (_emptySpaceItems != null && _emptySpaceItems.Length > 0)
|
||||
{
|
||||
foreach (var emp in _emptySpaceItems)
|
||||
{
|
||||
if (emp == null) continue;
|
||||
emp.Size = new Size(tWidth, RowHeight);
|
||||
emp.Location = new Point(BorderPadding, y);
|
||||
y += RowHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
lcgbModuleMain.EndUpdate();
|
||||
lcgbModuleMain.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
// 모듈 데이터가 아직 없을 때 참조를 위한 더미(구조 동일)
|
||||
private ref CsDeviceData.DeviceModuleData GetDummyModuleRef()
|
||||
{
|
||||
// static 필드로 유지해도 무방
|
||||
return ref dummy;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region EVENT BRIDGE
|
||||
|
||||
private void SetCmdEvent(
|
||||
int sId, int cmd, int index, int flag,
|
||||
ref CsDeviceData.DeviceModuleData.DeviceParamData aParam,
|
||||
ref CsDeviceData.DeviceModuleData.DeviceCalibration aCalib)
|
||||
{
|
||||
var handler = OnCommand; // 레이스 방지
|
||||
handler?.Invoke(sId, cmd, index, flag, ref aParam, ref aCalib);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region TIMER EVENT
|
||||
private void tmrDisplay_Tick(object sender, EventArgs e)
|
||||
{
|
||||
DisplayTotalValue();
|
||||
DisplayStatusAndAlarm();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DISPLAY FUNCTION
|
||||
private void DisplayTotalValue()
|
||||
{
|
||||
if (!_active || _deviceData?.TotalData == null)
|
||||
return;
|
||||
|
||||
var fw = _deviceData.TotalData.IdentData?.FwVerStr ?? "N/A";
|
||||
var modules = _config != null ? _config.ModuleQty : _moduleQty;
|
||||
|
||||
rtGraph.Text = $"Real Time Graph - V{fw} - {modules} Modules";
|
||||
|
||||
dgTotalVoltage.Text = $"{_totalVoltage:0.0}";
|
||||
dgTotalCurrent.Text = $"{_totalCurrent:0.0}";
|
||||
dgTotalTemp.Text = $"{_totalTemperature:0.0}";
|
||||
dgTotalSOC.Text = $"{_totalSoc:0.0}";
|
||||
dgTotalSOH.Text = $"{_totalSoh:0.0}";
|
||||
|
||||
chartVI?.Invalidate();
|
||||
}
|
||||
|
||||
private void DisplayStatusAndAlarm()
|
||||
{
|
||||
if (_deviceData?.TotalData == null)
|
||||
return;
|
||||
|
||||
if (_deviceData.TotalData.CommFail)
|
||||
{
|
||||
lbStatus.Text = "OFF-LINE";
|
||||
lbStatus.ForeColor = Colors.Offline;
|
||||
lbAlarm.Text = "OFF-LINE";
|
||||
lbAlarm.ForeColor = Colors.Offline;
|
||||
return;
|
||||
}
|
||||
|
||||
// Operating Status
|
||||
var st = (OperatingStatus)(_deviceData.TotalData.StatusData?.status ?? -1);
|
||||
switch (st)
|
||||
{
|
||||
case OperatingStatus.Standby:
|
||||
lbStatus.Text = "STANDBY";
|
||||
lbStatus.ForeColor = Colors.Default;
|
||||
break;
|
||||
case OperatingStatus.Charging:
|
||||
lbStatus.Text = "CHARGING";
|
||||
lbStatus.ForeColor = Colors.Charging;
|
||||
break;
|
||||
case OperatingStatus.Discharging:
|
||||
lbStatus.Text = "DISCHARGING";
|
||||
lbStatus.ForeColor = Colors.Discharging;
|
||||
break;
|
||||
case OperatingStatus.Floating:
|
||||
lbStatus.Text = "FLOATING";
|
||||
lbStatus.ForeColor = Colors.Default;
|
||||
break;
|
||||
default:
|
||||
lbStatus.Text = "UNKNOWN";
|
||||
lbStatus.ForeColor = Colors.Default;
|
||||
break;
|
||||
}
|
||||
|
||||
// Battery Status
|
||||
var bs = (BatteryStatus)(_deviceData.TotalData.StatusData?.batteryStatus ?? -1);
|
||||
switch (bs)
|
||||
{
|
||||
case BatteryStatus.Normal:
|
||||
lbAlarm.Text = "NORMAL";
|
||||
lbAlarm.ForeColor = Colors.Normal;
|
||||
break;
|
||||
case BatteryStatus.Warning:
|
||||
lbAlarm.Text = "WARNING";
|
||||
lbAlarm.ForeColor = Colors.Warning;
|
||||
break;
|
||||
case BatteryStatus.Fault:
|
||||
lbAlarm.Text = "FAULT";
|
||||
lbAlarm.ForeColor = Colors.Fault;
|
||||
break;
|
||||
case BatteryStatus.WarmingUp:
|
||||
lbAlarm.Text = "WARMING UP";
|
||||
lbAlarm.ForeColor = Colors.Warning;
|
||||
break;
|
||||
case BatteryStatus.FaultAntiTheftComm:
|
||||
lbAlarm.Text = "FAULT (Anti-Theft Comm.)";
|
||||
lbAlarm.ForeColor = Colors.Fault;
|
||||
break;
|
||||
case BatteryStatus.FaultAntiTheftGyro:
|
||||
lbAlarm.Text = "FAULT (Anti-Theft Gyro)";
|
||||
lbAlarm.ForeColor = Colors.Fault;
|
||||
break;
|
||||
default:
|
||||
lbAlarm.Text = $"UNKNOWN ({_deviceData.TotalData.StatusData?.batteryStatus})";
|
||||
lbAlarm.ForeColor = Colors.Fault;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user