초기 커밋.

This commit is contained in:
2025-12-19 13:59:34 +09:00
parent 1c0b03f88c
commit 79fea6964b
184 changed files with 94471 additions and 0 deletions

View File

@@ -0,0 +1,607 @@
using LFP_Manager.DataStructure;
using LFP_Manager.Function;
using LFP_Manager.Utils;
using SnmpSharpNet;
using System;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
namespace LFP_Manager.Threads
{
// 콜백 델리게이트
public delegate void SetResult(string result, bool error);
public delegate void DataUpdate(object sender, ref CsDeviceData.DeviceModuleData rData);
class CsSnmpThread
{
#region VARIABLES ()
private CsDeviceData.DeviceModuleData ModuleData;
private CommConfig Config;
// 스레드/토큰 기반 (Abort 제거)
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private Task _snmpTask;
// 상태 보호용 락
private readonly object _stateLock = new object();
// 실행 상태
private bool snmpThreadHold = true; // true = 정지, false = 동작
private string targetIP = "";
private bool AutoSnmpTx = true;
// OID 순회
private int OidIndex = 0;
private int OidMax = 0;
private string[] OidList = Array.Empty<string>();
// 통신 타임아웃 카운터
private int CommTimeOut = 0;
// 결과 메시지 (레거시 호환)
private bool snmpDataRecv = false;
private string snmpResult = "";
// 대기중 SET 명령 (race 방지 위해 락으로 보호)
private int _pendingModeInt = 0;
private int _pendingModeStr = 0;
private UInt32 _pendingValue = 0;
private string _pendingString = null;
private long _lastUpdateTicks = 0;
private const int UpdateMinIntervalMs = 120;
// 이벤트
public event DataUpdate OnUpdate = null;
public event SetResult OnSetResult = null;
#endregion
#region CONSTRUCTORS
public CsSnmpThread(CommConfig aConfig, ref CsDeviceData.DeviceModuleData mData)
{
Config = aConfig;
ModuleData = mData;
OidList = CsSnmpConstData.SnmpOidInfo ?? Array.Empty<string>();
OidMax = OidList.Length;
OidIndex = 0;
_snmpTask = Task.Factory.StartNew(
() => snmpThreadFuncA(_cts.Token),
_cts.Token,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
}
#endregion
#region PUBLIC API
/// <summary>
/// 안전 종료 (Abort 제거)
/// </summary>
public void disposeThread()
{
Stop();
_cts.Cancel();
try { _snmpTask?.Wait(1500); } catch { /* ignore */ }
}
/// <summary>
/// 스레드 실행 시작/재시작
/// </summary>
public void Start(string IP, bool autoTx)
{
lock (_stateLock)
{
targetIP = IP ?? "";
AutoSnmpTx = autoTx;
snmpThreadHold = false;
}
}
/// <summary>
/// 스레드 일시정지
/// </summary>
public void Stop()
{
lock (_stateLock)
{
snmpThreadHold = true;
}
}
/// <summary>
/// true면 정지 상태
/// </summary>
public bool GetStatus()
{
lock (_stateLock) { return snmpThreadHold; }
}
/// <summary>
/// 레거시: 마지막 결과 문자열 반환 후 플래그 리셋
/// </summary>
public string GetResult()
{
lock (_stateLock)
{
if (snmpDataRecv)
{
snmpDataRecv = false;
return snmpResult;
}
}
return "";
}
public void SetAutoTx(bool autoTx)
{
lock (_stateLock) { AutoSnmpTx = autoTx; }
}
public string GetSnmpOidInfo(int index)
{
if (index < 0 || index >= OidMax) return null;
return OidList[index];
}
/// <summary>
/// 정수형 SET 명령 예약
/// </summary>
public void SetDataBySnmp(int mode, UInt32 value)
{
lock (_stateLock)
{
_pendingModeInt = mode;
_pendingValue = value;
}
}
/// <summary>
/// 문자열 SET 명령 예약
/// </summary>
public void SetDataBySnmp(int mode, string value)
{
lock (_stateLock)
{
_pendingModeStr = mode;
_pendingString = value;
}
}
#endregion
#region COMM. THREAD
private void snmpThreadFuncA(CancellationToken ct)
{
var wait = new ManualResetEventSlim(false);
int backoffMs = 50; // 정상 루프 기본 지연
while (!ct.IsCancellationRequested)
{
bool hold;
string ip;
bool autoTx;
lock (_stateLock)
{
hold = snmpThreadHold;
ip = targetIP;
autoTx = AutoSnmpTx;
}
if (hold || string.IsNullOrWhiteSpace(ip))
{
// 대기
wait.Wait(TimeSpan.FromMilliseconds(500), ct);
continue;
}
// Target 생성 및 재사용 범위
if (!TryParseIp(ip, out var addr))
{
OnSetResultSafe($"Invalid IP address: {ip}", true);
wait.Wait(TimeSpan.FromMilliseconds(1000), ct);
continue;
}
var param = CreateAgentParams(); // 커뮤니티/버전 통일
using (var target = new UdpTarget(addr, 161, 1000, 1))
{
var pdu = new Pdu(PduType.Get);
while (!ct.IsCancellationRequested)
{
lock (_stateLock)
{
if (snmpThreadHold) break;
}
// 1) 대기중 SET 우선 처리
int modeInt, modeStr;
UInt32 setVal;
string setStr;
lock (_stateLock)
{
modeInt = _pendingModeInt;
modeStr = _pendingModeStr;
setVal = _pendingValue;
setStr = _pendingString;
_pendingModeInt = 0;
_pendingModeStr = 0;
_pendingString = null;
}
if (modeInt != 0 || modeStr != 0)
{
int mode = (modeInt != 0) ? modeInt : modeStr;
bool err = SetCmdbySnmp(target, mode, setVal, setStr);
backoffMs = err ? Math.Min(backoffMs * 2, 2000) : 100; // 에러 시 백오프
continue;
}
// 2) GET 폴링
autoTx = AutoSnmpTx;
if (!autoTx)
{
wait.Wait(TimeSpan.FromMilliseconds(200), ct);
continue;
}
if (OidMax == 0)
{
OnSetResultSafe("SNMP OID list is empty.", true);
wait.Wait(TimeSpan.FromMilliseconds(1000), ct);
continue;
}
int idx = NextOidIndex();
string oid = GetSnmpOidInfo(idx);
if (string.IsNullOrEmpty(oid))
{
Thread.Sleep(100);
continue;
}
pdu.VbList.Clear();
pdu.VbList.Add(oid);
try
{
target.Timeout = 500;
OnSetResultSafe(csLog.trx_msg_print(
$"SEND {target.Address}: OID [{pdu.VbList[0].Oid}]", 0), false);
var packet = (SnmpV1Packet)target.Request(pdu, param);
if (packet == null)
{
HandleNoResponse(ref backoffMs);
continue;
}
if (packet.Pdu.ErrorStatus != 0)
{
string oidTxt = (packet.Pdu.VbList.Count > 0)
? packet.Pdu.VbList[0].Oid.ToString()
: "(no vb)";
OnSetResultSafe(csLog.trx_msg_print(
$"Error in SNMP reply. Error {packet.Pdu.ErrorStatus} index {packet.Pdu.ErrorIndex} - {oidTxt}", 1), true);
HandleCommFailStep(ref backoffMs);
continue;
}
if (packet.Pdu.VbList.Count == 0)
{
OnSetResultSafe("SNMP reply has empty VbList.", true);
HandleCommFailStep(ref backoffMs);
continue;
}
var vb = packet.Pdu.VbList[0];
OnSetResultSafe(csLog.trx_msg_print(
$"RECV {target.Address}: OID [{vb.Oid}]: [{vb.Value}]", 1), false);
// 특정 트리만 반영 (1.3.6.1.2.1.15.*) → 필요에 맞게 수정하세요.
if (TryParseShelfOid(vb, out uint leaf))
{
if (vb.Value.Type == AsnType.OCTETSTRING)
{
try
{
csMakeDataFunction.SetSnmpData((int)leaf, vb.Value.ToString(), ref ModuleData);
}
catch (Exception ex)
{
OnSetResultSafe($"{NowPrefix()} {ex.Message}", true);
}
}
CalcAvgTemperatureModule(ref ModuleData, (uint)ModuleData.tempQty);
CheckShelfCommFail(ref ModuleData);
var nowTicks = Environment.TickCount;
if (nowTicks - _lastUpdateTicks >= UpdateMinIntervalMs)
{
OnUpdate?.Invoke(this, ref ModuleData);
_lastUpdateTicks = nowTicks;
}
}
CommTimeOut = 0;
ModuleData.CommFail = false;
backoffMs = 50;
Thread.Sleep(backoffMs);
}
catch (Exception ex)
{
string curOid = pdu.VbList.Count > 0 ? pdu.VbList[0].Oid.ToString() : "(no oid)";
OnSetResultSafe(csLog.trx_msg_print(
$"Exception in SNMP reply. OID {curOid}: {ex.Message}", 1), true);
HandleCommFailStep(ref backoffMs);
}
} // inner while
} // using target
Thread.Sleep(500);
} // outer while
}
#endregion
#region SNMP HELPERS
private AgentParameters CreateAgentParams()
{
// 커뮤니티/버전 통일: v1 + "public" (필요시 Ver2로 변경)
// Ver2가 가능하면: new AgentParameters(SnmpVersion.Ver2, new OctetString("public"));
return new AgentParameters(SnmpVersion.Ver1, new OctetString("admin"));
}
private bool SetCmdbySnmp(UdpTarget sTarget, int mode, UInt32 value, string svalue)
{
bool resultError = false;
var SetPdu = new Pdu(PduType.Set);
SetPdu.VbList.Clear();
// 모드별 OID 매핑 (기존 코드 유지)
switch (mode)
{
case 1: // Reset Command
SetPdu.VbList.Add(new Oid(".1.3.6.1.4.1.33.13.1.0"), new Integer32(Convert.ToInt32(value)));
break;
case 2: // Module Quantity Set
SetPdu.VbList.Add(new Oid(".1.3.6.1.4.1.33.2.4.0"), new Integer32(Convert.ToInt32(value)));
break;
case 3: // Alarm Output Mode
SetPdu.VbList.Add(new Oid(".1.3.6.1.4.1.33.2.5.0"), new Integer32(Convert.ToInt32(value)));
break;
case 4: // Manufacture Date
SetPdu.VbList.Add(new Oid(".1.3.6.1.4.1.33.2.6.0"), new Integer32(Convert.ToInt32(value)));
break;
case 5: // BMS Serial Number
SetPdu.VbList.Add(new Oid(".1.3.6.1.4.1.33.2.7.0"), new OctetString(svalue ?? ""));
break;
default:
OnSetResultSafe($"Unsupported SET mode: {mode}", true);
return true;
}
try
{
var aparam = CreateAgentParams();
var result = sTarget.Request(SetPdu, aparam) as SnmpV1Packet;
if (result == null)
{
OnSetResultSafe($"{NowPrefix()} No response received from SNMP agent.", true);
return true;
}
if (result.Pdu.ErrorStatus != 0)
{
string oidTxt = (result.Pdu.VbList.Count > 0)
? result.Pdu.VbList[0].Oid.ToString()
: "(no vb)";
OnSetResultSafe($"Error in SNMP SET. Error {result.Pdu.ErrorStatus} index {result.Pdu.ErrorIndex} - {oidTxt}", true);
return true;
}
var vbOk = result.Pdu.VbList.Count > 0 ? result.Pdu.VbList[0] : null;
var val = vbOk != null ? vbOk.Value.ToString() : "(no vb)";
OnSetResultSafe($"SNMP SET OK. OID [{vbOk?.Oid}] -> [{val}]", false);
return false;
}
catch (Exception ex)
{
OnSetResultSafe($"{NowPrefix()} Exception: {ex.Message}", true);
resultError = true;
}
return resultError;
}
private static bool TryParseIp(string ip, out IPAddress addr)
{
if (IPAddress.TryParse(ip, out addr)) return true;
try
{
var host = Dns.GetHostEntry(ip);
addr = host?.AddressList?.FirstOrDefault(a => a.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);
return addr != null;
}
catch { addr = null; return false; }
}
private int NextOidIndex()
{
// 간단한 라운드로빈
int next = Interlocked.Increment(ref OidIndex);
if (OidMax == 0) return 0;
next %= OidMax;
if (next < 0) next += OidMax;
return next;
}
private static bool TryParseShelfOid(Vb vb, out uint leaf)
{
leaf = 0;
if (vb?.Oid == null) return false;
// OID를 문자열로 받아 숫자 배열로 파싱
// 예: ".1.3.6.1.2.1.15.7.0" 또는 "1.3.6.1.2.1.15.7.0"
string oidStr = vb.Oid.ToString();
if (string.IsNullOrWhiteSpace(oidStr)) return false;
// 선행 '.' 제거 후 분해
var parts = oidStr.Trim('.').Split('.');
if (parts.Length < 8) return false;
// 숫자 배열로 변환 (변환 실패 시 false)
uint[] ids = new uint[parts.Length];
for (int i = 0; i < parts.Length; i++)
{
if (!uint.TryParse(parts[i], out ids[i]))
return false;
}
// 예시 트리: 1.3.6.1.2.1.15.x.*
if (ids[0] == 1 && ids[1] == 3 && ids[2] == 6 &&
ids[3] == 1 && ids[4] == 2 && ids[5] == 1 &&
ids[6] == 15)
{
leaf = ids[7];
return true;
}
return false;
}
private void HandleNoResponse(ref int backoffMs)
{
lock (_stateLock)
{
if (!snmpDataRecv)
{
snmpResult = $"{NowPrefix()} No response received from SNMP agent.";
snmpDataRecv = true;
OnSetResult?.Invoke(snmpResult, true);
}
}
CommTimeOut++;
if (CommTimeOut >= 10)
{
ModuleData.Reset();
ModuleData.CommFail = true;
var nowTicks = Environment.TickCount;
if (nowTicks - _lastUpdateTicks >= UpdateMinIntervalMs)
{
OnUpdate?.Invoke(this, ref ModuleData);
_lastUpdateTicks = nowTicks;
}
CommTimeOut = 10;
}
Thread.Sleep(backoffMs = Math.Min(backoffMs * 2, 2000));
}
private void HandleCommFailStep(ref int backoffMs)
{
CommTimeOut++;
if (CommTimeOut >= 10)
{
ModuleData.Reset();
ModuleData.CommFail = true;
var nowTicks = Environment.TickCount;
if (nowTicks - _lastUpdateTicks >= UpdateMinIntervalMs)
{
OnUpdate?.Invoke(this, ref ModuleData);
_lastUpdateTicks = nowTicks;
}
CommTimeOut = 10;
}
Thread.Sleep(backoffMs = Math.Min(backoffMs * 2, 2000));
}
private void OnSetResultSafe(string msg, bool error)
{
try { OnSetResult?.Invoke(msg, error); } catch { /* swallow */ }
}
private static string NowPrefix() => DateTime.Now.ToString("[yyyy-MM-dd HH:mm:ss] ");
#endregion
#region DOMAIN HELPERS ( /)
private void CheckShelfCommFail(ref CsDeviceData.DeviceModuleData mData)
{
// 상태코드 0x0003 → ShelfCommFail=true (기존 가정 유지)
if (mData.StatusData.batteryStatus == 0x0003) { mData.ShelfCommFail = true; }
else { mData.ShelfCommFail = false; }
}
// 기존 코드 유지 (평균/최대/최소 계산)
static void CalcAvgTemperatureModule(ref CsDeviceData.DeviceModuleData mData, UInt32 tSize)
{
short i, j;
int sum;
TMinMax min, max;
min = new TMinMax();
max = new TMinMax();
sum = 0;
max.value = 0;
max.num = 0;
min.value = 0;
min.num = 0;
j = 0;
for (i = 0; i < tSize; i++)
{
if (j == 0)
{
max.value = mData.ValueData.CellTemperature[i];
max.num = i;
min.value = mData.ValueData.CellTemperature[i];
min.num = i;
}
if (max.value < mData.ValueData.CellTemperature[i])
{
max.value = mData.ValueData.CellTemperature[i];
max.num = i;
}
if (min.value > mData.ValueData.CellTemperature[i])
{
min.value = mData.ValueData.CellTemperature[i];
min.num = i;
}
sum += mData.ValueData.CellTemperature[i];
j++;
}
if (j == 0) { mData.AvgData.avgTemp = 0; }
else { mData.AvgData.avgTemp = (short)(sum / j); }
mData.AvgData.maxTemp = max.value;
mData.AvgData.maxTempNum = max.num;
mData.AvgData.minTemp = min.value;
mData.AvgData.minTempNum = min.num;
}
#endregion
}
}