초기 커밋.
This commit is contained in:
607
LFP_Manager/Threads/CsSnmpThread.cs
Normal file
607
LFP_Manager/Threads/CsSnmpThread.cs
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user