Files
DT_BR_GUI/LFP_Manager/Threads/CsSnmpThread124050.cs
2025-12-17 12:40:51 +09:00

583 lines
19 KiB
C#

using LFP_Manager.DataStructure;
using LFP_Manager.Function;
using LFP_Manager.Utils;
using SnmpSharpNet;
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
namespace LFP_Manager.Threads
{
/// <summary>
/// 향상된 SNMP 통신 스레드 클래스 - 안전하고 효율적인 리소스 관리
/// </summary>
public class CsSnmpThread124050 : IDisposable
{
#region CONSTANTS
private const int MAX_COMM_TIMEOUT = 20;
private const int SNMP_TIMEOUT_MS = 1000;
private const int SNMP_RETRIES = 1;
private const int SNMP_PORT = 161;
private const int THREAD_SLEEP_MS = 50;
private const int OPERATION_SLEEP_MS = 500;
private const string DEFAULT_COMMUNITY = "admin";
#endregion
#region FIELDS
private DeviceSystemData _systemData;
private readonly CommConfig _config;
private readonly string[] _oidList;
private readonly object _lockObject = new object();
private Task _snmpTask;
private CancellationTokenSource _cancellationTokenSource;
private volatile bool _disposed = false;
private string _targetIP = "";
private bool _autoSnmpTx = true;
private int _oidIndex = 0;
private int _commTimeOut = 0;
// SNMP SET 관련 필드
private volatile int _setSnmpMode = 0;
private volatile int _setSnmpMode1 = 0;
private volatile uint _setSnmpValue = 0;
private volatile string _setSnmpStringData = "";
// 이벤트 및 델리게이트
public event DataUpdate OnUpdate;
public event SetResult OnSetResult;
#endregion
#region CONSTRUCTORS
public CsSnmpThread124050()
{
_cancellationTokenSource = new CancellationTokenSource();
_snmpTask = Task.Run(() => SnmpThreadFunction(_cancellationTokenSource.Token));
}
public CsSnmpThread124050(CommConfig config, ref DeviceSystemData systemData)
{
_config = config ?? throw new ArgumentNullException(nameof(config));
_systemData = systemData ?? throw new ArgumentNullException(nameof(systemData));
_oidList = CsSnmpConstData124050.SnmpOidInfo;
if (_oidList == null || _oidList.Length == 0)
{
throw new InvalidOperationException("OID 목록이 비어있습니다.");
}
_cancellationTokenSource = new CancellationTokenSource();
_snmpTask = Task.Run(() => SnmpThreadFunction(_cancellationTokenSource.Token));
}
#endregion
#region PUBLIC METHODS
/// <summary>
/// SNMP 통신 시작
/// </summary>
public void Start(string ip, bool autoTx = true)
{
if (string.IsNullOrWhiteSpace(ip))
throw new ArgumentException("IP 주소가 유효하지 않습니다.", nameof(ip));
lock (_lockObject)
{
_targetIP = ip;
_autoSnmpTx = autoTx;
}
}
/// <summary>
/// SNMP 통신 중지
/// </summary>
public void Stop()
{
lock (_lockObject)
{
_autoSnmpTx = false;
}
}
/// <summary>
/// 현재 상태 반환
/// </summary>
public bool IsRunning => _autoSnmpTx && !_disposed;
/// <summary>
/// 자동 전송 모드 설정
/// </summary>
public void SetAutoTx(bool autoTx)
{
lock (_lockObject)
{
_autoSnmpTx = autoTx;
}
}
/// <summary>
/// SNMP SET 명령 (정수 값)
/// </summary>
public void SetDataBySnmp(int mode, uint value)
{
lock (_lockObject)
{
_setSnmpMode = mode;
_setSnmpValue = value;
}
}
/// <summary>
/// SNMP SET 명령 (문자열 값)
/// </summary>
public void SetDataBySnmp(int mode, string value)
{
lock (_lockObject)
{
_setSnmpMode1 = mode;
_setSnmpStringData = value ?? "";
}
}
/// <summary>
/// 리소스 해제
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
#region PRIVATE METHODS
protected virtual void Dispose(bool disposing)
{
if (!_disposed && disposing)
{
_cancellationTokenSource?.Cancel();
try
{
_snmpTask?.Wait(1000); // 5초 대기
}
catch (OperationCanceledException)
{
// 정상적인 취소
}
catch (AggregateException ex) when (ex.InnerExceptions.Count == 1 && ex.InnerException is OperationCanceledException)
{
// 정상적인 취소
}
_cancellationTokenSource?.Dispose();
_disposed = true;
}
}
/// <summary>
/// 메인 SNMP 스레드 함수
/// </summary>
private async Task SnmpThreadFunction(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
try
{
if (string.IsNullOrEmpty(_targetIP))
{
await Task.Delay(OPERATION_SLEEP_MS, cancellationToken);
continue;
}
using (var target = CreateSnmpTarget())
{
await ProcessSnmpOperations(target, cancellationToken);
}
await Task.Delay(OPERATION_SLEEP_MS, cancellationToken);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
LogError($"SNMP 스레드 오류: {ex.Message}");
await Task.Delay(1000, cancellationToken);
}
}
}
/// <summary>
/// SNMP 타겟 생성
/// </summary>
private UdpTarget CreateSnmpTarget()
{
var agent = new IpAddress(_targetIP);
return new UdpTarget((IPAddress)agent, SNMP_PORT, SNMP_TIMEOUT_MS, SNMP_RETRIES);
}
/// <summary>
/// SNMP 작업 처리
/// </summary>
private async Task ProcessSnmpOperations(UdpTarget target, CancellationToken cancellationToken)
{
// SET 명령 처리
if (_setSnmpMode != 0)
{
await ExecuteSetCommand(target, _setSnmpMode, _setSnmpValue, _setSnmpStringData);
_setSnmpMode = 0;
return;
}
if (_setSnmpMode1 != 0)
{
await ExecuteSetCommand(target, _setSnmpMode1, _setSnmpValue, _setSnmpStringData);
_setSnmpMode1 = 0;
return;
}
// GET 명령 처리
if (_autoSnmpTx)
{
await ExecuteGetOperations(target, cancellationToken);
}
}
/// <summary>
/// SNMP SET 명령 실행
/// </summary>
private async Task<bool> ExecuteSetCommand(UdpTarget target, int mode, uint value, string stringValue)
{
try
{
var setPdu = new Pdu(PduType.Set);
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(stringValue));
break;
default:
LogError($"알 수 없는 SET 모드: {mode}");
return false;
}
var param = new AgentParameters(SnmpVersion.Ver1, new OctetString(DEFAULT_COMMUNITY));
var result = await Task.Run(() => target.Request(setPdu, param) as SnmpV1Packet);
return ProcessSetResult(result);
}
catch (Exception ex)
{
LogError($"SET 명령 실행 중 오류: {ex.Message}");
OnSetResult?.Invoke($"SET 명령 실행 실패: {ex.Message}", true);
return false;
}
}
/// <summary>
/// SET 결과 처리
/// </summary>
private bool ProcessSetResult(SnmpV1Packet result)
{
if (result == null)
{
LogError("SNMP 에이전트로부터 응답이 없습니다.");
OnSetResult?.Invoke("SNMP 에이전트 응답 없음", true);
return false;
}
if (result.Pdu.ErrorStatus != 0)
{
var errorMsg = $"SNMP 응답 오류: Error {result.Pdu.ErrorStatus} index {result.Pdu.ErrorIndex}";
LogError(errorMsg);
OnSetResult?.Invoke(errorMsg, true);
return false;
}
var resultValue = result.Pdu.VbList[0].Value.ToString();
var successMsg = $"SNMP SET 성공: {resultValue}";
OnSetResult?.Invoke(successMsg, false);
return true;
}
/// <summary>
/// SNMP GET 작업 실행
/// </summary>
private async Task ExecuteGetOperations(UdpTarget target, CancellationToken cancellationToken)
{
while (_autoSnmpTx && !cancellationToken.IsCancellationRequested)
{
if (_setSnmpMode != 0 || _setSnmpMode1 != 0)
break;
try
{
var pdu = new Pdu(PduType.Get);
pdu.VbList.Add(GetCurrentOid());
var param = new AgentParameters(SnmpVersion.Ver1, new OctetString(DEFAULT_COMMUNITY));
LogTransaction($"SEND {target.Address}: OID [{pdu.VbList[0].Oid}]");
var result = await Task.Run(() => target.Request(pdu, param) as SnmpV1Packet);
if (await ProcessGetResult(result, target))
{
_commTimeOut = 0;
_systemData.CommFail = false;
}
else
{
HandleCommunicationTimeout();
}
MoveToNextOid();
await Task.Delay(THREAD_SLEEP_MS, cancellationToken);
}
catch (Exception ex)
{
LogError($"GET 작업 중 오류: {ex.Message}");
HandleCommunicationTimeout();
break;
}
}
UpdateSystemStatus();
}
/// <summary>
/// GET 결과 처리
/// </summary>
private async Task<bool> ProcessGetResult(SnmpV1Packet result, UdpTarget target)
{
return await Task.Run(() =>
{
if (result == null)
{
LogError("SNMP 에이전트로부터 응답이 없습니다.");
return false;
}
if (result.Pdu.ErrorStatus != 0)
{
LogError($"SNMP 응답 오류: Error {result.Pdu.ErrorStatus} index {result.Pdu.ErrorIndex}");
return false;
}
LogTransaction($"RECV {target.Address}: OID [{result.Pdu.VbList[0].Oid}]: [{result.Pdu.VbList[0].Value}]");
// OID 검증 및 데이터 처리
var oidArray = (uint[])result.Pdu.VbList[0].Oid;
if (IsValidTargetOid(oidArray))
{
ProcessValidData(result, oidArray);
return true;
}
return false;
});
}
/// <summary>
/// 유효한 타겟 OID인지 확인
/// </summary>
private bool IsValidTargetOid(uint[] oidArray)
{
return oidArray.Length >= 8 &&
oidArray[0] == 1 && oidArray[1] == 3 && oidArray[2] == 6 &&
oidArray[3] == 1 && oidArray[4] == 2 && oidArray[5] == 1 && oidArray[6] == 15;
}
/// <summary>
/// 유효한 데이터 처리
/// </summary>
private void ProcessValidData(SnmpV1Packet result, uint[] oidArray)
{
try
{
if (result.Pdu.VbList[0].Value.Type == AsnType.OCTETSTRING)
{
var value = result.Pdu.VbList[0].Value.ToString();
csMakeDataFunction124050.SetSnmpData((int)oidArray[7], value, ref _systemData);
}
CalcAvgTemperatureModule(ref _systemData, (uint)_systemData.tempQty);
CheckShelfCommFail(ref _systemData);
OnUpdate?.Invoke(this, ref _systemData);
}
catch (Exception ex)
{
LogError($"데이터 처리 중 오류: {ex.Message}");
}
}
/// <summary>
/// 통신 타임아웃 처리
/// </summary>
private void HandleCommunicationTimeout()
{
_commTimeOut++;
if (_commTimeOut >= MAX_COMM_TIMEOUT)
{
_systemData.CommFail = true;
InitModuleData();
_commTimeOut = MAX_COMM_TIMEOUT;
}
}
/// <summary>
/// 시스템 상태 업데이트
/// </summary>
private void UpdateSystemStatus()
{
CheckShelfCommFail(ref _systemData);
OnUpdate?.Invoke(this, ref _systemData);
}
/// <summary>
/// 현재 OID 반환
/// </summary>
private string GetCurrentOid()
{
return _oidList?[_oidIndex] ?? "";
}
/// <summary>
/// 다음 OID로 이동
/// </summary>
private void MoveToNextOid()
{
_oidIndex = (_oidIndex + 1) % (_oidList?.Length ?? 1);
}
/// <summary>
/// 모듈 데이터 초기화
/// </summary>
private void InitModuleData()
{
if (_systemData?.ValueData == null) return;
_systemData.ValueData.voltageOfPack = 0;
_systemData.ValueData.current = 0;
_systemData.ValueData.averageCurrent = 0;
_systemData.ValueData.rSOC = 0;
_systemData.ValueData.stateOfHealth = 0;
Array.Clear(_systemData.ValueData.CellVoltage, 0, _systemData.ValueData.CellVoltage.Length);
Array.Clear(_systemData.ValueData.CellTemperature, 0, _systemData.ValueData.CellTemperature.Length);
if (_systemData.AvgData != null)
{
_systemData.AvgData.avgCellVoltage = 0;
_systemData.AvgData.diffCellVoltage = 0;
_systemData.AvgData.maxCellVoltage = 0;
_systemData.AvgData.minCellVoltage = 0;
_systemData.AvgData.maxCellNum = 0;
_systemData.AvgData.minCellNum = 0;
_systemData.AvgData.avgTemp = 0;
_systemData.AvgData.diffTemp = 0;
_systemData.AvgData.maxTemp = 0;
_systemData.AvgData.minTemp = 0;
_systemData.AvgData.maxTempNum = 0;
_systemData.AvgData.minTempNum = 0;
}
}
/// <summary>
/// 선반 통신 실패 확인
/// </summary>
private void CheckShelfCommFail(ref DeviceSystemData systemData)
{
systemData.ShelfCommFail = systemData.StatusData.batteryStatus == 0x0003;
}
/// <summary>
/// 평균 온도 계산
/// </summary>
private static void CalcAvgTemperatureModule(ref DeviceSystemData systemData, uint tempSize)
{
if (systemData?.ValueData?.CellTemperature == null || systemData.AvgData == null)
return;
var temperatures = systemData.ValueData.CellTemperature;
var validCount = Math.Min((int)tempSize, temperatures.Length);
if (validCount == 0)
{
systemData.AvgData.avgTemp = 0;
return;
}
var sum = 0;
var max = new { value = temperatures[0], index = 0 };
var min = new { value = temperatures[0], index = 0 };
for (int i = 0; i < validCount; i++)
{
var temp = temperatures[i];
sum += temp;
if (temp > max.value)
max = new { value = temp, index = i };
if (temp < min.value)
min = new { value = temp, index = i };
}
systemData.AvgData.avgTemp = (short)(sum / validCount);
systemData.AvgData.maxTemp = max.value;
systemData.AvgData.maxTempNum = max.index;
systemData.AvgData.minTemp = min.value;
systemData.AvgData.minTempNum = min.index;
}
/// <summary>
/// 트랜잭션 로그
/// </summary>
private void LogTransaction(string message)
{
try
{
var logMessage = csLog.trx_msg_print(message, 1);
OnSetResult?.Invoke(logMessage, false);
}
catch
{
// 로그 실패 시 무시
}
}
/// <summary>
/// 오류 로그
/// </summary>
private void LogError(string message)
{
try
{
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
var logMessage = $"[{timestamp}] {message}";
OnSetResult?.Invoke(logMessage, true);
}
catch
{
// 로그 실패 시 무시
}
}
#endregion
}
}