초기 커밋.
This commit is contained in:
583
LFP_Manager/Threads/CsSnmpThread124050.cs
Normal file
583
LFP_Manager/Threads/CsSnmpThread124050.cs
Normal file
@@ -0,0 +1,583 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user