Files
JP_KDDI_LFPS_48100/LFP_Manager/Threads/csDbThread.cs
2025-12-19 13:59:34 +09:00

1015 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
{
string modelname = csConstData.UART_MODEL[Config.UartModelIndex];
csDbUtils.LogDbCreate(modelname);
DbCreate(modelname, DateTime.Now);
// 최초 활성 진입
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];
// 마지막 기록 시각 (UTC 권장; 로컬도 무방하지만 일관성 위해 UTC 사용)
DateTime lastLogUtc = DateTime.UtcNow;
try
{
CurrAlarmHistory = csDbUtils.GetBmsAlarmDataByDataTable(modelName, 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;
// “초 변경” + “주기 정합” 검사
// ★ 경과 시간 기반 주기 체크 (초 단위 주기 모두 정확)
if ((nowUtc - lastLogUtc).TotalSeconds >= periodSec)
{
// 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))
lastLogUtc = nowUtc;
}
}
try
{
// Alarm History Log
modelName = csConstData.UART_MODEL[Config.UartModelIndex];
csDbUtils.LogDbCreate(modelName);
for (int i = 0; i < mQty; i++)
{
if (CheckStatusAndAlarm(i, modelName, Config, DeviceData.ModuleData[i], OldDevData.ModuleData[i]))
{
CurrAlarmHistory = csDbUtils.GetBmsAlarmDataByDataTable(modelName, 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);
}
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(modelName, 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
}
}