1014 lines
54 KiB
C#
1014 lines
54 KiB
C#
using LFP_Manager.DataStructure;
|
|
using LFP_Manager.Function;
|
|
using LFP_Manager.Utils;
|
|
using System;
|
|
using System.ComponentModel;
|
|
using System.Data;
|
|
using System.Data.SQLite;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Windows.Forms;
|
|
|
|
namespace LFP_Manager.Threads
|
|
{
|
|
class csDbThread
|
|
{
|
|
#region DELEGATE
|
|
public delegate void UpdateAlarmHistory(DataTable aAlarmHistoryData);
|
|
#endregion
|
|
|
|
#region VARIABLES
|
|
|
|
CommConfig Config = new CommConfig();
|
|
CsDeviceData DeviceData;
|
|
|
|
CsDeviceData OldDevData;
|
|
|
|
DataTable CurrAlarmHistory;
|
|
|
|
private Thread _dbProc; // ← 생성자에서 만들지 않음
|
|
private volatile bool _dbThreadEnd = true;
|
|
private volatile bool Active = false;
|
|
|
|
private readonly object _stateLock = new object();
|
|
private readonly ISynchronizeInvoke _ui; // 보통 Form 또는 Control
|
|
|
|
private int mQty = 1;
|
|
|
|
public event EventHandler<PrintEventArgs> OnPrint;
|
|
public event UpdateAlarmHistory OnUpdateAlarmHistory = null;
|
|
|
|
#endregion
|
|
|
|
#region CONSTRUCTORS
|
|
|
|
public csDbThread(CommConfig aConfig,
|
|
ISynchronizeInvoke uiInvoker,
|
|
CsDeviceData aModuleData)
|
|
{
|
|
Config = aConfig;
|
|
DeviceData = aModuleData;
|
|
OldDevData = new CsDeviceData();
|
|
|
|
_ui = uiInvoker;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Stop();
|
|
}
|
|
|
|
public bool Start(CommConfig aConfig)
|
|
{
|
|
if (_dbProc != null && _dbProc.IsAlive) return false;
|
|
|
|
Config = aConfig;
|
|
mQty = (Config.CommType == csConstData.CommType.COMM_RS485) ? Config.ModuleQty : 1;
|
|
|
|
if (_dbProc != null && _dbProc.IsAlive)
|
|
return true; // 이미 동작 중
|
|
|
|
_dbThreadEnd = false;
|
|
Active = true;
|
|
|
|
// DB 경로 준비 (예외는 루프에서 잡히지만 시작 시도 전 준비)
|
|
try
|
|
{
|
|
csDbUtils.DbCreateLOG(Config);
|
|
string modelname = csConstData.UART_MODEL[Config.UartModelIndex];
|
|
|
|
// 최초 활성 진입
|
|
if (Active)
|
|
{
|
|
// START 정보 기록
|
|
_ = BmsAlarmDataInsert(modelname, (int)DbConstData.Alarm.START, (int)DbConstData.Flag.INFORMATION, DeviceData.ModuleData[0], DateTime.Now);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
RaiseOnPrint($"DB init error: {ex.Message}");
|
|
}
|
|
|
|
_dbProc = new Thread(dbThreadProcess)
|
|
{
|
|
IsBackground = true,
|
|
Name = "DbThread"
|
|
};
|
|
_dbProc.Start();
|
|
return true;
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
Active = false;
|
|
_dbThreadEnd = true;
|
|
|
|
var t = _dbProc;
|
|
if (t != null && t.IsAlive)
|
|
{
|
|
for (int i = 0; i < 5 && t.IsAlive; i++)
|
|
t.Join(200); // 최대 약 1초
|
|
}
|
|
_dbProc = null;
|
|
|
|
// ★ 종료 직전 1회 저장/알람 기록
|
|
FlushNow("Stop");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region SAFETY RAISE HELPER
|
|
private void RaiseOnUpdateAlarmHistory(DataTable dt)
|
|
{
|
|
if (_ui != null && _ui.InvokeRequired)
|
|
_ui.BeginInvoke(new Action(() => OnUpdateAlarmHistory?.Invoke(dt)), null);
|
|
else
|
|
OnUpdateAlarmHistory?.Invoke(dt);
|
|
}
|
|
|
|
private void RaiseOnPrint(string msg)
|
|
{
|
|
var handler = OnPrint;
|
|
if (handler == null) return;
|
|
|
|
var args = new PrintEventArgs(0, msg);
|
|
|
|
if (_ui != null && _ui.InvokeRequired)
|
|
{
|
|
try { _ui.BeginInvoke(new Action(() => handler(this, args)), null); }
|
|
catch { }
|
|
}
|
|
else
|
|
{
|
|
handler(this, args);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region DB THREAD
|
|
|
|
private void dbThreadProcess()
|
|
{
|
|
int periodSec = Math.Max(1, Config.DbLogPeriod); // 0 방지
|
|
string modelName = csConstData.UART_MODEL[Config.UartModelIndex];
|
|
|
|
int lastLoggedSecond = -1; // ★ 마지막으로 저장한 "초"
|
|
|
|
try
|
|
{
|
|
CurrAlarmHistory = csDbUtils.GetBmsAlarmDataByDataTable(Config, DateTime.Now, "");
|
|
RaiseOnUpdateAlarmHistory(CurrAlarmHistory); // UI 마샬링 버전
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
csLog.SystemDbErrorLog(Config, ex.Message, DateTime.Now, Application.ExecutablePath);
|
|
}
|
|
|
|
while (!_dbThreadEnd)
|
|
{
|
|
if (Active)
|
|
{
|
|
DateTime nowLocal = DateTime.Now;
|
|
DateTime nowUtc = DateTime.UtcNow;
|
|
|
|
int sec = nowUtc.Second;
|
|
|
|
// “초 변경” + “주기 정합” 검사
|
|
// ★ 경과 시간 기반 주기 체크 (초 단위 주기 모두 정확)
|
|
if ((sec % periodSec) == 0 && sec != lastLoggedSecond)
|
|
{
|
|
// Database Log Process
|
|
try
|
|
{
|
|
CsDeviceData snapshot;
|
|
lock (_stateLock) { snapshot = DeviceData.DeepCopy(); }
|
|
|
|
int count = Math.Max(1, mQty);
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
// RS-485는 i+1, 단일 UART는 1
|
|
snapshot.ModuleData[i].mNo =
|
|
(Config.CommType == csConstData.CommType.COMM_RS485) ? (i + 1) : 1;
|
|
|
|
csDbUtils.BmsLogDataInsert(modelName, ref snapshot.ModuleData[i], nowLocal, 1);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
RaiseOnPrint($"DB loop error: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
// ★ 마지막 로그 시각 갱신 (드리프트 최소화를 원하면 lastLogUtc += TimeSpan.FromSeconds(periodSec))
|
|
lastLoggedSecond = sec;
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
// Alarm History Log
|
|
csDbUtils.DbCreateLOG(Config);
|
|
|
|
for (int i = 0; i < mQty; i++)
|
|
{
|
|
if (CheckStatusAndAlarm(i, modelName, Config, DeviceData.ModuleData[i], OldDevData.ModuleData[i]))
|
|
{
|
|
CurrAlarmHistory = csDbUtils.GetBmsAlarmDataByDataTable(Config, DateTime.Now, "");
|
|
RaiseOnUpdateAlarmHistory(CurrAlarmHistory);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
csLog.SystemDbErrorLog(Config, ex.Message, DateTime.Now, Application.ExecutablePath);
|
|
}
|
|
}
|
|
Thread.Sleep(100);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region BMS ALARM DB INSERT FUNCTION
|
|
public static bool BmsAlarmDataInsert1(string modelname, CommConfig aConfig, int aAlarmCode, int aAlarmStatus, CsDeviceData.DeviceModuleData mData, DateTime dateTime)
|
|
{
|
|
bool resultFlag = false;
|
|
string result = "";
|
|
//public static string LogDbFileNameFormat = @"\{0}\{1}_{2}_LOG.DB";
|
|
string dbFilename = string.Format(csDbUtils.LogDbFileNameFormat
|
|
, String.Format("{0:yyMM}", DateTime.Now)
|
|
, String.Format("{0:yyMMdd}", DateTime.Now)
|
|
, modelname
|
|
);
|
|
string dbFilePath = Path.GetDirectoryName(Application.ExecutablePath) + csDbUtils.LogDbFilePath + dbFilename;
|
|
|
|
if (Directory.Exists(Path.GetDirectoryName(dbFilePath)) == false)
|
|
{
|
|
throw new Exception("No Log DB file path");
|
|
}
|
|
|
|
if (File.Exists(dbFilePath) == false)
|
|
{
|
|
throw new Exception(String.Format("No Log DB file - BmsAlarmDataInsertByModule ({0})", dbFilename));
|
|
}
|
|
|
|
// Open database
|
|
string strConn = @"Data Source=" + dbFilePath;
|
|
using (SQLiteConnection connection = new SQLiteConnection(strConn))
|
|
{
|
|
try
|
|
{
|
|
connection.Open();
|
|
csDbUtils.BeginTran(connection);
|
|
|
|
// Insert System data
|
|
using (SQLiteCommand command = connection.CreateCommand())
|
|
{
|
|
//sSQL = "insert into TrendTable ( TrendStamp, TagName, TagValue) Values ( " + IntToStr(stamp) + "," + name + "," + value + ");";
|
|
command.CommandText = String.Format("INSERT INTO TAlarmHistory (");
|
|
command.CommandText += "create_date" // 0
|
|
+ ", alarm_code" // 1
|
|
+ ", alarm_status" // 2
|
|
+ ", alarm_cname" // 3
|
|
+ ", alarm_sname" // 4
|
|
+ ", module_no" // 5
|
|
+ ", ct_no" // 6
|
|
+ ", alarm_param_1" // 7
|
|
+ ", alarm_param_2" // 8
|
|
+ ", alarm_param_3" // 9
|
|
;
|
|
command.CommandText = command.CommandText
|
|
+ ")"
|
|
+ " Values (";
|
|
|
|
int total = 10;
|
|
|
|
for (int i = 0; i < (total - 1); i++) command.CommandText += "?,";
|
|
command.CommandText += "?);";
|
|
|
|
SQLiteParameter[] p = new SQLiteParameter[total];
|
|
|
|
for (int i = 0; i < total; i++)
|
|
{
|
|
if (i == 0)
|
|
{
|
|
p[i] = new SQLiteParameter(DbType.DateTime);
|
|
}
|
|
else
|
|
{
|
|
p[i] = new SQLiteParameter();
|
|
}
|
|
command.Parameters.Add(p[i]);
|
|
}
|
|
|
|
int j = 0;
|
|
p[j++].Value = dateTime; // 0 create_date
|
|
p[j++].Value = aAlarmCode; // 1 alarm_code
|
|
p[j++].Value = aAlarmStatus; // 2 alarm_status
|
|
p[j++].Value = DbConstData.GetAlarmName(aAlarmCode); // 3 alarm_cname
|
|
p[j++].Value = DbConstData.GetFlagName(aAlarmStatus); // 4 alarm_sname
|
|
p[j++].Value = mData.mNo; // 5 module_no
|
|
switch (aAlarmCode)
|
|
{
|
|
case 0: // Cell Under Voltage
|
|
p[j++].Value = mData.AvgData.minCellNum; // 6 ct_no
|
|
p[j++].Value = mData.AvgData.minCellVoltage; // 7 alarm_param_1
|
|
p[j++].Value = 0; // 8 alarm_param_2
|
|
p[j++].Value = 0; // 9 alarm_param_3
|
|
break;
|
|
case 1: // Cell Over Voltage
|
|
p[j++].Value = mData.AvgData.maxCellNum; // 6 ct_no
|
|
p[j++].Value = mData.AvgData.maxCellVoltage; // 7 alarm_param_1
|
|
p[j++].Value = 0; // 8 alarm_param_2
|
|
p[j++].Value = 0; // 9 alarm_param_3
|
|
break;
|
|
case 2: // Module Under Voltage
|
|
p[j++].Value = 0; // 6 ct_no
|
|
p[j++].Value = mData.ValueData.voltage; // 7 alarm_param_1
|
|
p[j++].Value = 0; // 8 alarm_param_2
|
|
p[j++].Value = 0; // 9 alarm_param_3
|
|
break;
|
|
case 3: // Module Over Voltage
|
|
p[j++].Value = 0; // 6 ct_no
|
|
p[j++].Value = mData.ValueData.voltage; // 7 alarm_param_1
|
|
p[j++].Value = 0; // 8 alarm_param_2
|
|
p[j++].Value = 0; // 9 alarm_param_3
|
|
break;
|
|
case 4: // Charge High Temperature
|
|
p[j++].Value = mData.AvgData.maxTempNum; // 6 ct_no
|
|
p[j++].Value = mData.AvgData.maxTemp; // 7 alarm_param_1
|
|
p[j++].Value = 0; // 8 alarm_param_2
|
|
p[j++].Value = 0; // 9 alarm_param_3
|
|
break;
|
|
case 5: // Discharge High Temperature
|
|
p[j++].Value = mData.AvgData.maxTempNum; // 6 ct_no
|
|
p[j++].Value = mData.AvgData.maxTemp; // 7 alarm_param_1
|
|
p[j++].Value = 0; // 8 alarm_param_2
|
|
p[j++].Value = 0; // 9 alarm_param_3
|
|
break;
|
|
case 6: // Charge Low Temperature
|
|
p[j++].Value = mData.AvgData.minTempNum; // 6 ct_no
|
|
p[j++].Value = mData.AvgData.minTemp; // 7 alarm_param_1
|
|
p[j++].Value = 0; // 8 alarm_param_2
|
|
p[j++].Value = 0; // 9 alarm_param_3
|
|
break;
|
|
case 7: // Discharge Low Temperature
|
|
p[j++].Value = mData.AvgData.minTempNum; // 6 ct_no
|
|
p[j++].Value = mData.AvgData.minTemp; // 7 alarm_param_1
|
|
p[j++].Value = 0; // 8 alarm_param_2
|
|
p[j++].Value = 0; // 9 alarm_param_3
|
|
break;
|
|
case 8: // Charge Over Current
|
|
p[j++].Value = 0; // 6 ct_no
|
|
p[j++].Value = mData.ValueData.current; // 7 alarm_param_1
|
|
p[j++].Value = 0; // 8 alarm_param_2
|
|
p[j++].Value = 0; // 9 alarm_param_3
|
|
break;
|
|
case 9: // Discharge Over Current
|
|
p[j++].Value = 0; // 6 ct_no
|
|
p[j++].Value = mData.ValueData.current; // 7 alarm_param_1
|
|
p[j++].Value = 0; // 8 alarm_param_2
|
|
p[j++].Value = 0; // 9 alarm_param_3
|
|
break;
|
|
case 10: // Low SOC
|
|
p[j++].Value = 0; // 6 ct_no
|
|
p[j++].Value = mData.ValueData.SOC; // 7 alarm_param_1
|
|
p[j++].Value = 0; // 8 alarm_param_2
|
|
p[j++].Value = 0; // 9 alarm_param_3
|
|
break;
|
|
case 11: // Low SOH
|
|
p[j++].Value = 0; // 6 ct_no
|
|
p[j++].Value = mData.ValueData.SOH; // 7 alarm_param_1
|
|
p[j++].Value = 0; // 8 alarm_param_2
|
|
p[j++].Value = 0; // 9 alarm_param_3
|
|
break;
|
|
case 12: // Cell Voltage Difference
|
|
p[j++].Value = 0; // 6 ct_no
|
|
p[j++].Value = mData.AvgData.minCellVoltage; // 7 alarm_param_1
|
|
p[j++].Value = mData.AvgData.maxCellVoltage; // 8 alarm_param_2
|
|
p[j++].Value = 0; // 9 alarm_param_3
|
|
break;
|
|
case 13: // Short Circuit
|
|
p[j++].Value = 0; // 6 ct_no
|
|
p[j++].Value = mData.ValueData.voltage; // 7 alarm_param_1
|
|
p[j++].Value = mData.ValueData.current; // 8 alarm_param_2
|
|
p[j++].Value = 0; // 9 alarm_param_3
|
|
break;
|
|
case 14: // Module Comm Fail
|
|
p[j++].Value = 0; // 6 ct_no
|
|
p[j++].Value = mData.ValueData.voltage; // 7 alarm_param_1
|
|
p[j++].Value = mData.ValueData.current; // 8 alarm_param_2
|
|
p[j++].Value = 0; // 9 alarm_param_3
|
|
break;
|
|
|
|
case 15: // Cell Voltage Measurement Error
|
|
p[j++].Value = 0; // 6 ct_no
|
|
p[j++].Value = mData.AvgData.minCellVoltage; // 7 alarm_param_1
|
|
p[j++].Value = mData.AvgData.maxCellVoltage; // 8 alarm_param_2
|
|
p[j++].Value = 0; // 9 alarm_param_3
|
|
break;
|
|
case 16: // Temperature Measurement Error
|
|
p[j++].Value = 0; // 6 ct_no
|
|
p[j++].Value = mData.AvgData.minTemp; // 7 alarm_param_1
|
|
p[j++].Value = mData.AvgData.maxTemp; // 8 alarm_param_2
|
|
p[j++].Value = 0; // 9 alarm_param_3
|
|
break;
|
|
case 17: // Battery Cell Unbalance
|
|
p[j++].Value = 0; // 6 ct_no
|
|
p[j++].Value = mData.AvgData.minCellVoltage; // 7 alarm_param_1
|
|
p[j++].Value = mData.AvgData.maxCellVoltage; // 8 alarm_param_2
|
|
p[j++].Value = 0; // 9 alarm_param_3
|
|
break;
|
|
case 18: // Emergency Alarm
|
|
p[j++].Value = 0; // 6 ct_no
|
|
p[j++].Value = mData.ValueData.voltage; // 7 alarm_param_1
|
|
p[j++].Value = mData.ValueData.current; // 8 alarm_param_2
|
|
p[j++].Value = 0; // 9 alarm_param_3
|
|
break;
|
|
|
|
case 21: // STS_STAND_BY
|
|
case 22: // STS_CHARING
|
|
case 23: // STS_DISCHARGING
|
|
case 24: // STS_FLOAT_CHARGING
|
|
p[j++].Value = 0; // 6 ct_no
|
|
p[j++].Value = mData.ValueData.voltage; // 7 alarm_param_1
|
|
p[j++].Value = mData.ValueData.current; // 8 alarm_param_2
|
|
p[j++].Value = 0; // 9 alarm_param_3
|
|
break;
|
|
default: // STS_UNKNOWN
|
|
p[j++].Value = 0; // 6 ct_no
|
|
p[j++].Value = 0; // 7 alarm_param_1
|
|
p[j++].Value = 0; // 8 alarm_param_2
|
|
p[j++].Value = 0; // 9 alarm_param_3
|
|
break;
|
|
}
|
|
|
|
command.ExecuteNonQuery();
|
|
csDbUtils.CommitTran(connection);
|
|
|
|
resultFlag = true;
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
result = e.Message;
|
|
}
|
|
finally
|
|
{
|
|
if (connection.State == ConnectionState.Open)
|
|
{
|
|
connection.Close();
|
|
}
|
|
if (result != "")
|
|
{
|
|
throw new Exception(result);
|
|
}
|
|
}
|
|
}
|
|
return resultFlag;
|
|
}
|
|
#endregion
|
|
|
|
#region ALARM STATUS CHECK
|
|
private bool CheckStatusAndAlarm(int mNo, string modelName, CommConfig aConfig, CsDeviceData.DeviceModuleData nModule, CsDeviceData.DeviceModuleData oModule)
|
|
{
|
|
bool eventFlag = false;
|
|
|
|
if (nModule == null || oModule == null)
|
|
return false;
|
|
|
|
// 현재/이전 경고/보호/에러 비트
|
|
bool[] wNew = csUtils.UInt16ToBitArray((ushort)nModule.StatusData.warning);
|
|
bool[] pNew = csUtils.UInt16ToBitArray((ushort)nModule.StatusData.protect);
|
|
bool[] eNew = csUtils.UInt16ToBitArray((ushort)nModule.StatusData.errorCode);
|
|
|
|
bool[] wOld = csUtils.UInt16ToBitArray((ushort)oModule.StatusData.warning);
|
|
bool[] pOld = csUtils.UInt16ToBitArray((ushort)oModule.StatusData.protect);
|
|
bool[] eOld = csUtils.UInt16ToBitArray((ushort)oModule.StatusData.errorCode);
|
|
|
|
var alarmData = new THistoryAlarmData();
|
|
DateTime now = DateTime.Now;
|
|
|
|
// 시스템 통신 상태 변화 기록
|
|
if (oModule.CommFail != nModule.CommFail)
|
|
{
|
|
eventFlag |= BmsAlarmDataInsert(modelName, (int)DbConstData.Alarm.COMM_FAIL,
|
|
nModule.CommFail ? (int)DbConstData.Flag.WARNING : (int)DbConstData.Flag.RELEASE, nModule, now);
|
|
oModule.CommFail = nModule.CommFail;
|
|
}
|
|
// 0..15 비트에 대한 경고/보호 엣지 검사
|
|
for (int i = 0; i < 16; i++)
|
|
{
|
|
alarmData.newFdata = pNew[i];
|
|
alarmData.oldFdata = pOld[i];
|
|
alarmData.newWdata = wNew[i];
|
|
alarmData.oldWdata = wOld[i];
|
|
|
|
switch (i)
|
|
{
|
|
case 0: // Over Voltage 0X0001
|
|
eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.OVER_VOLTAGE, nModule, now);
|
|
break;
|
|
case 1: // Cell Over Voltage 0x0002
|
|
eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.CELL_OVER_VOLTAGE, nModule, now);
|
|
break;
|
|
case 2: // Under Voltage 0x0004
|
|
eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.UNDER_VOLTAGE, nModule, now);
|
|
break;
|
|
case 3: // Cell Under Voltage 0x0008
|
|
eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.CELL_UNDER_VOLTAGE, nModule, now);
|
|
break;
|
|
case 4: // Charge Over Current 0x0010
|
|
eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.CHARGE_OVER_CURRENT, nModule, now);
|
|
break;
|
|
case 5: // Discharge Over Current 0x0020
|
|
eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.DISCHARGE_OVER_CURRENT, nModule, now);
|
|
break;
|
|
case 6: // Reserved 0x0040
|
|
break;
|
|
case 7: // MOSFET Over Temperature 0x0080
|
|
eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.MOSFET_OVER_TERMPERATURE, nModule, now);
|
|
break;
|
|
case 8: // Charge Over Temperature 0x0100
|
|
eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.CHARGE_OVER_TEMPERATURE, nModule, now);
|
|
break;
|
|
case 9: // Discharge Over Temperature 0x0200
|
|
eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.DISCHARGE_OVER_TEMPERATURE, nModule, now);
|
|
break;
|
|
case 10: // Charge Low Temperature 0x0400
|
|
eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.CHARGE_LOW_TEMPERATURE, nModule, now);
|
|
break;
|
|
case 11: // Discharge Low Temperature 0x0800
|
|
eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.DISCHARGE_LOW_TEMPERATURE, nModule, now);
|
|
break;
|
|
case 12: // Low Capacity(SOC) 0x1000
|
|
eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.LOW_SOC, nModule, now);
|
|
break;
|
|
case 13: // Short Circuit 0x2000
|
|
eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.SHORT_CIRCUIT, nModule, now);
|
|
break;
|
|
case 14: // CB OFF 0x4000
|
|
eventFlag |= InsertAlarmHistory(modelName, alarmData, (int)DbConstData.Alarm.CB_OFF, nModule, now);
|
|
break;
|
|
}
|
|
}
|
|
// Update last alarm status
|
|
oModule.StatusData.warning = nModule.StatusData.warning;
|
|
oModule.StatusData.protect = nModule.StatusData.protect;
|
|
|
|
// 0..15 비트에 대한 Error 엣지 검사
|
|
for (int i = 0; i < 16; i++)
|
|
{
|
|
alarmData.newEdata = eNew[i];
|
|
alarmData.oldEdata = eOld[i];
|
|
|
|
switch (i)
|
|
{
|
|
case 0: // Voltage Measurement Error 0X0001
|
|
eventFlag |= InsertErrorHistory(modelName, alarmData, (int)DbConstData.Alarm.VOLTAGE_MEASUREMENT_ERROR, nModule, now);
|
|
break;
|
|
case 1: // Temperature Measurement Error 0x0002
|
|
eventFlag |= InsertErrorHistory(modelName, alarmData, (int)DbConstData.Alarm.TEMPERATURE_MEASUREMENT_ERROR, nModule, now);
|
|
break;
|
|
case 4: // Battery Cell Unbalance 0x0010
|
|
eventFlag |= InsertErrorHistory(modelName, alarmData, (int)DbConstData.Alarm.BATTERY_CELL_UNBALANCE, nModule, now);
|
|
break;
|
|
case 5: // Emergency Alarm 0x0020
|
|
eventFlag |= InsertErrorHistory(modelName, alarmData, (int)DbConstData.Alarm.EMERGENCY_ALARM, nModule, now);
|
|
break;
|
|
default:
|
|
// Reserved Error
|
|
break;
|
|
}
|
|
}
|
|
// Update last alarm status
|
|
oModule.StatusData.errorCode = nModule.StatusData.errorCode;
|
|
|
|
// Check Status Change
|
|
if (oModule.StatusData.status != nModule.StatusData.status)
|
|
{
|
|
switch (nModule.StatusData.status)
|
|
{
|
|
case 0: // STANDBY
|
|
eventFlag = BmsAlarmDataInsert(modelName, (int)DbConstData.Alarm.STS_STAND_BY,
|
|
(int)DbConstData.Flag.STS_CHANGED, nModule, now);
|
|
break;
|
|
case 1: // CHARGING
|
|
eventFlag = BmsAlarmDataInsert(modelName, (int)DbConstData.Alarm.STS_CHARGING,
|
|
(int)DbConstData.Flag.STS_CHANGED, nModule, now);
|
|
break;
|
|
case 2: // DISCHARGING
|
|
eventFlag = BmsAlarmDataInsert(modelName, (int)DbConstData.Alarm.STS_DISCHARGING,
|
|
(int)DbConstData.Flag.STS_CHANGED, nModule, now);
|
|
break;
|
|
case 4: // PROTECT
|
|
eventFlag = BmsAlarmDataInsert(modelName, (int)DbConstData.Alarm.STS_PROTECTED,
|
|
(int)DbConstData.Flag.STS_CHANGED, nModule, now);
|
|
break;
|
|
default: // UNKNOWN
|
|
eventFlag = BmsAlarmDataInsert(modelName, (int)DbConstData.Alarm.STS_UNKNOWN,
|
|
(int)DbConstData.Flag.STS_CHANGED, nModule, now);
|
|
break;
|
|
}
|
|
oModule.StatusData.status = nModule.StatusData.status;
|
|
}
|
|
|
|
if (eventFlag)
|
|
{
|
|
csDbUtils.BmsLogDataInsert(modelName, ref nModule, now, 1, 1);
|
|
}
|
|
return eventFlag;
|
|
}
|
|
|
|
private bool InsertAlarmHistory(string modelName, THistoryAlarmData AlarmData, int alarmCode, CsDeviceData.DeviceModuleData mData, DateTime dateTime)
|
|
{
|
|
bool resultFlag = false;
|
|
|
|
// Check protection
|
|
if (AlarmData.oldFdata != AlarmData.newFdata)
|
|
{
|
|
if (AlarmData.newFdata)
|
|
{
|
|
// Faulty
|
|
resultFlag = BmsAlarmDataInsert(modelName, alarmCode, (int)DbConstData.Flag.PROTECT, mData, dateTime);
|
|
}
|
|
else
|
|
{
|
|
if (AlarmData.oldWdata != AlarmData.newWdata)
|
|
{
|
|
if (AlarmData.newWdata)
|
|
{
|
|
// Warning
|
|
resultFlag = BmsAlarmDataInsert(modelName, alarmCode, (int)DbConstData.Flag.WARNING, mData, dateTime);
|
|
}
|
|
else
|
|
{
|
|
// Release
|
|
resultFlag = BmsAlarmDataInsert(modelName, alarmCode, (int)DbConstData.Flag.RELEASE, mData, dateTime);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (AlarmData.newWdata == false)
|
|
{
|
|
// Release
|
|
resultFlag = BmsAlarmDataInsert(modelName, alarmCode, (int)DbConstData.Flag.RELEASE, mData, dateTime);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (AlarmData.oldWdata != AlarmData.newWdata)
|
|
{
|
|
if (AlarmData.newWdata)
|
|
{
|
|
// Warning
|
|
resultFlag = BmsAlarmDataInsert(modelName, alarmCode, (int)DbConstData.Flag.WARNING, mData, dateTime);
|
|
//IDInsert("", alarmCode, CsDbConstData.DB_ALARM.FLAG_WARNING);
|
|
}
|
|
else
|
|
{
|
|
if (AlarmData.newFdata == false)
|
|
{
|
|
// Release
|
|
resultFlag = BmsAlarmDataInsert(modelName, alarmCode, (int)DbConstData.Flag.RELEASE, mData, dateTime);
|
|
//IDInsert("", alarmCode, CsDbConstData.DB_ALARM.FLAG_RELEASE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return resultFlag;
|
|
}
|
|
|
|
private bool InsertErrorHistory(string modelName, THistoryAlarmData AlarmData, int alarmCode, CsDeviceData.DeviceModuleData mData, DateTime dateTime)
|
|
{
|
|
if (AlarmData.oldEdata == AlarmData.newEdata) return false;
|
|
|
|
int status = AlarmData.newEdata ? (int)DbConstData.Flag.ERROR
|
|
: (int)DbConstData.Flag.RELEASE;
|
|
|
|
return BmsAlarmDataInsert(modelName, alarmCode, status, mData, dateTime);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region BMS ALARM DB INSERT FUNCTION (C# 7.3 compatible)
|
|
public static bool BmsAlarmDataInsert(
|
|
string modelname,
|
|
int aAlarmCode,
|
|
int aAlarmStatus,
|
|
CsDeviceData.DeviceModuleData mData,
|
|
DateTime dateTime)
|
|
{
|
|
// 파일명/경로 안전 조합
|
|
string dbFilename = string.Format(
|
|
csDbUtils.LogDbFileNameFormat,
|
|
DateTime.Now.ToString("yyMM"),
|
|
DateTime.Now.ToString("yyMMdd"),
|
|
modelname);
|
|
|
|
string exeDir = Path.GetDirectoryName(Application.ExecutablePath);
|
|
if (string.IsNullOrEmpty(exeDir))
|
|
exeDir = AppDomain.CurrentDomain.BaseDirectory;
|
|
|
|
string logPath = (csDbUtils.LogDbFilePath ?? string.Empty)
|
|
.TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
|
string dbDir = csUtils.CombineSafe(exeDir, logPath);
|
|
string dbFilePath = csUtils.CombineSafe(dbDir, dbFilename);
|
|
|
|
if (!Directory.Exists(Path.GetDirectoryName(dbFilePath)))
|
|
throw new Exception("No Log DB file path");
|
|
|
|
if (!File.Exists(dbFilePath))
|
|
throw new Exception(string.Format("No Log DB file - BmsAlarmDataInsertByModule ({0})", dbFilename));
|
|
|
|
// INSERT 대상 컬럼(총 10개)
|
|
// create_date, alarm_code, alarm_status, alarm_cname, alarm_sname,
|
|
// module_no, ct_no, alarm_param_1, alarm_param_2, alarm_param_3
|
|
const int total = 10;
|
|
|
|
using (var connection = new SQLiteConnection(@"Data Source=" + dbFilePath))
|
|
{
|
|
connection.Open();
|
|
// ★ 성능/동시성 PRAGMA 적용 (WAL + busy_timeout 등) — C# 7.3 호환
|
|
csDbUtils.SqlitePragmaHelper.ApplyPragmas(connection, true); // forWriter: true
|
|
|
|
using (var tran = connection.BeginTransaction())
|
|
{
|
|
try
|
|
{
|
|
using (var command = connection.CreateCommand())
|
|
{
|
|
command.Transaction = tran;
|
|
|
|
command.CommandText =
|
|
"INSERT INTO TAlarmHistory (" +
|
|
"create_date, alarm_code, alarm_status, alarm_cname, alarm_sname, " +
|
|
"module_no, ct_no, alarm_param_1, alarm_param_2, alarm_param_3" +
|
|
") VALUES (" + string.Join(",", Enumerable.Repeat("?", total)) + ");";
|
|
|
|
// 파라미터 생성
|
|
var p = new SQLiteParameter[total];
|
|
for (int i = 0; i < total; i++)
|
|
{
|
|
p[i] = (i == 0) ? new SQLiteParameter(DbType.DateTime) : new SQLiteParameter();
|
|
command.Parameters.Add(p[i]);
|
|
}
|
|
|
|
// 공통 메타
|
|
int j = 0;
|
|
p[j++].Value = string.Format("{0:yyyy-MM-dd HH:mm:ss}", dateTime); // 0 create_date
|
|
p[j++].Value = aAlarmCode; // 1 alarm_code
|
|
p[j++].Value = aAlarmStatus; // 2 alarm_status
|
|
p[j++].Value = DbConstData.GetAlarmName(aAlarmCode); // 3 alarm_cname
|
|
p[j++].Value = DbConstData.GetFlagName(aAlarmStatus); // 4 alarm_sname
|
|
p[j++].Value = mData.mNo; // 5 module_no
|
|
|
|
// 알람별 세부 파라미터 계산
|
|
int ctNo = 0;
|
|
double param1 = 0, param2 = 0, param3 = 0;
|
|
|
|
switch (aAlarmCode)
|
|
{
|
|
case 0: // Cell Under Voltage
|
|
ctNo = mData.AvgData.minCellNum;
|
|
param1 = mData.AvgData.minCellVoltage; param2 = 0;
|
|
break;
|
|
case 1: // Cell Over Voltage
|
|
ctNo = mData.AvgData.maxCellNum;
|
|
param1 = mData.AvgData.maxCellVoltage; param2 = 0;
|
|
break;
|
|
case 2: // Module Under Voltage
|
|
case 3: // Module Over Voltage
|
|
ctNo = 0;
|
|
param1 = mData.ValueData.voltage; param2 = 0;
|
|
break;
|
|
case 4: // Charge High Temperature
|
|
case 5: // Discharge High Temperature
|
|
ctNo = mData.AvgData.maxTempNum;
|
|
param1 = mData.AvgData.maxTemp; param2 = 0;
|
|
break;
|
|
case 6: // Charge Low Temperature
|
|
case 7: // Discharge Low Temperature
|
|
ctNo = mData.AvgData.minTempNum;
|
|
param1 = mData.AvgData.minTemp; param2 = 0;
|
|
break;
|
|
case 8: // Charge Over Current
|
|
case 9: // Discharge Over Current
|
|
ctNo = 0;
|
|
param1 = mData.ValueData.current; param2 = 0;
|
|
break;
|
|
case 10: // Low SOC
|
|
ctNo = 0;
|
|
param1 = mData.ValueData.SOC; param2 = 0;
|
|
break;
|
|
case 11: // Low SOH
|
|
ctNo = 0;
|
|
try
|
|
{
|
|
var sohProp = mData.ValueData.GetType().GetProperty("SOH");
|
|
if (sohProp != null)
|
|
{
|
|
object v = sohProp.GetValue(mData.ValueData, null);
|
|
param1 = Convert.ToDouble(v);
|
|
}
|
|
else
|
|
{
|
|
param1 = mData.ValueData.SOC;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
param1 = mData.ValueData.SOC;
|
|
}
|
|
param2 = 0;
|
|
break;
|
|
case 12: // Cell Voltage Difference
|
|
ctNo = 0;
|
|
param1 = mData.AvgData.minCellVoltage; param2 = mData.AvgData.maxCellVoltage;
|
|
break;
|
|
case 13: // Short Circuit
|
|
ctNo = 0;
|
|
param1 = mData.ValueData.voltage; param2 = mData.ValueData.current;
|
|
break;
|
|
case 14: // Module Comm Fail
|
|
ctNo = 0;
|
|
param1 = 0; param2 = 0;
|
|
break;
|
|
case 15: // MOSFET Over Temerature
|
|
ctNo = 0;
|
|
param1 = mData.ValueData.MosTemperature; param2 = 0;
|
|
break;
|
|
case 16: // CB Off
|
|
ctNo = 0;
|
|
param1 = mData.ValueData.voltage; param2 = mData.ValueData.current;
|
|
break;
|
|
case 17: // Voltage Measurement Error
|
|
ctNo = 0;
|
|
param1 = mData.AvgData.minCellVoltage; param2 = mData.AvgData.maxCellVoltage;
|
|
break;
|
|
case 18: // Temperature Measurement Error
|
|
ctNo = 0;
|
|
param1 = mData.AvgData.minTemp; param2 = mData.AvgData.maxTemp;
|
|
break;
|
|
case 19: // Battery Cell Unbalance
|
|
ctNo = 0;
|
|
param1 = mData.AvgData.minCellVoltage; param2 = mData.AvgData.maxCellVoltage;
|
|
break;
|
|
case 20: // Emergency Alarm
|
|
ctNo = 0;
|
|
param1 = mData.ValueData.voltage; param2 = mData.ValueData.current;
|
|
break;
|
|
|
|
// 상태 코드(스냅샷 저장) — 오타 보정: CHARING → CHARGING
|
|
case 21: // STS_STAND_BY
|
|
case 22: // STS_CHARGING
|
|
case 23: // STS_DISCHARGING
|
|
case 24: // STS_FLOAT_CHARGING
|
|
case 25: // STS_PRE_CHARGING
|
|
ctNo = 0;
|
|
param1 = mData.ValueData.voltage; param2 = mData.ValueData.current;
|
|
break;
|
|
|
|
default: // STS_UNKNOWN 등
|
|
ctNo = 0; param1 = 0; param2 = 0; param3 = 0;
|
|
break;
|
|
}
|
|
|
|
p[j++].Value = ctNo; // 6 ct_no
|
|
p[j++].Value = param1; // 7 alarm_param_1
|
|
p[j++].Value = param2; // 8 alarm_param_2
|
|
p[j++].Value = param3; // 9 alarm_param_3
|
|
|
|
command.ExecuteNonQuery();
|
|
}
|
|
tran.Commit();
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
try { tran.Rollback(); } catch { /* ignore */ }
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
if (connection.State == ConnectionState.Open)
|
|
connection.Close();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region CREATE HISTORY DB TABLE
|
|
public static void DbCreate(string modelName, DateTime aDateTime)
|
|
{
|
|
// 일자 기준 DB 파일명 (yyMM/yyMMdd)
|
|
string dbFilename = string.Format(
|
|
csDbUtils.LogDbFileNameFormat,
|
|
aDateTime.ToString("yyMM"),
|
|
aDateTime.ToString("yyMMdd"),
|
|
modelName);
|
|
|
|
// 실행파일 경로 + 로그 DB 상대경로 + 파일명 안전 결합
|
|
string exeDir = Path.GetDirectoryName(Application.ExecutablePath);
|
|
if (string.IsNullOrEmpty(exeDir))
|
|
exeDir = AppDomain.CurrentDomain.BaseDirectory;
|
|
|
|
string logPath = (csDbUtils.LogDbFilePath ?? string.Empty)
|
|
.TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
|
string dbDir = csUtils.CombineSafe(exeDir, logPath);
|
|
string dbFilePath = csUtils.CombineSafe(dbDir, dbFilename);
|
|
|
|
if (Directory.Exists(System.IO.Path.GetDirectoryName(dbFilePath)) == false)
|
|
Directory.CreateDirectory(System.IO.Path.GetDirectoryName(dbFilePath));
|
|
|
|
if (File.Exists(dbFilePath) == false)
|
|
// Create database
|
|
SQLiteConnection.CreateFile(dbFilePath);
|
|
|
|
// Open database
|
|
string strConn = @"Data Source=" + dbFilePath;
|
|
using (var connection = new SQLiteConnection(strConn))
|
|
{
|
|
connection.Open();
|
|
try
|
|
{
|
|
// Create table
|
|
using (SQLiteCommand command = connection.CreateCommand())
|
|
{
|
|
command.CommandText = DbConstData.Database.CreateTable;
|
|
command.ExecuteNonQuery();
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
finally
|
|
{
|
|
connection.Close();
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region FLUSH ALARM HISTORY
|
|
// csDbThread 클래스 내부 어딘가(예: #region DB THREAD 아래)
|
|
private void FlushNow(string reason = null)
|
|
{
|
|
try
|
|
{
|
|
if (Config == null) return;
|
|
|
|
// 모델/수량 재확인
|
|
var modelName = csConstData.UART_MODEL[Config.UartModelIndex];
|
|
mQty = (Config.CommType == csConstData.CommType.COMM_RS485) ? Config.ModuleQty : 1;
|
|
|
|
// 스냅샷 확보
|
|
CsDeviceData snapshot;
|
|
lock (_stateLock)
|
|
{
|
|
snapshot = DeviceData != null ? DeviceData.DeepCopy() : null;
|
|
}
|
|
if (snapshot == null) return;
|
|
|
|
var now = DateTime.Now;
|
|
var anyAlarm = false;
|
|
|
|
// 모듈 값 1회 저장
|
|
int count = Math.Max(1, mQty);
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
// RS-485는 i+1, 단일 UART는 1
|
|
snapshot.ModuleData[i].mNo =
|
|
(Config.CommType == csConstData.CommType.COMM_RS485) ? (i + 1) : 1;
|
|
|
|
csDbUtils.BmsLogDataInsert(modelName, ref snapshot.ModuleData[i], now, 1);
|
|
}
|
|
|
|
// STOP 정보 기록
|
|
anyAlarm |= BmsAlarmDataInsert(modelName, (int)DbConstData.Alarm.STOP, (int)DbConstData.Flag.INFORMATION, snapshot.ModuleData[0], now);
|
|
|
|
if (anyAlarm)
|
|
{
|
|
CurrAlarmHistory = csDbUtils.GetBmsAlarmDataByDataTable(Config, DateTime.Now, "");
|
|
RaiseOnUpdateAlarmHistory(CurrAlarmHistory);
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(reason))
|
|
RaiseOnPrint($"DB flushed ({reason}).");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
RaiseOnPrint($"Flush error{(reason != null ? "(" + reason + ")" : "")}: {ex.Message}");
|
|
// 치명적 예외는 로깅만 하고 흐름 유지
|
|
csLog.SystemDbErrorLog(Config, ex.Message, DateTime.Now, Application.ExecutablePath);
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|