Files
JP_KDDI_LFPS_48100/LFP_Manager/Controls/ucMainStatus.cs
jkwoo e2b5223a39 V1.0.0.8 - 2025/12/30
* RS-485 Comm Improve
2025-12-30 13:33:14 +09:00

471 lines
16 KiB
C#

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
}
}