583 lines
19 KiB
C#
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
|
|
}
|
|
} |