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 { /// /// 향상된 SNMP 통신 스레드 클래스 - 안전하고 효율적인 리소스 관리 /// 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 /// /// SNMP 통신 시작 /// public void Start(string ip, bool autoTx = true) { if (string.IsNullOrWhiteSpace(ip)) throw new ArgumentException("IP 주소가 유효하지 않습니다.", nameof(ip)); lock (_lockObject) { _targetIP = ip; _autoSnmpTx = autoTx; } } /// /// SNMP 통신 중지 /// public void Stop() { lock (_lockObject) { _autoSnmpTx = false; } } /// /// 현재 상태 반환 /// public bool IsRunning => _autoSnmpTx && !_disposed; /// /// 자동 전송 모드 설정 /// public void SetAutoTx(bool autoTx) { lock (_lockObject) { _autoSnmpTx = autoTx; } } /// /// SNMP SET 명령 (정수 값) /// public void SetDataBySnmp(int mode, uint value) { lock (_lockObject) { _setSnmpMode = mode; _setSnmpValue = value; } } /// /// SNMP SET 명령 (문자열 값) /// public void SetDataBySnmp(int mode, string value) { lock (_lockObject) { _setSnmpMode1 = mode; _setSnmpStringData = value ?? ""; } } /// /// 리소스 해제 /// 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; } } /// /// 메인 SNMP 스레드 함수 /// 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); } } } /// /// SNMP 타겟 생성 /// private UdpTarget CreateSnmpTarget() { var agent = new IpAddress(_targetIP); return new UdpTarget((IPAddress)agent, SNMP_PORT, SNMP_TIMEOUT_MS, SNMP_RETRIES); } /// /// SNMP 작업 처리 /// 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); } } /// /// SNMP SET 명령 실행 /// private async Task 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; } } /// /// SET 결과 처리 /// 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; } /// /// SNMP GET 작업 실행 /// 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(); } /// /// GET 결과 처리 /// private async Task 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; }); } /// /// 유효한 타겟 OID인지 확인 /// 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; } /// /// 유효한 데이터 처리 /// 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}"); } } /// /// 통신 타임아웃 처리 /// private void HandleCommunicationTimeout() { _commTimeOut++; if (_commTimeOut >= MAX_COMM_TIMEOUT) { _systemData.CommFail = true; InitModuleData(); _commTimeOut = MAX_COMM_TIMEOUT; } } /// /// 시스템 상태 업데이트 /// private void UpdateSystemStatus() { CheckShelfCommFail(ref _systemData); OnUpdate?.Invoke(this, ref _systemData); } /// /// 현재 OID 반환 /// private string GetCurrentOid() { return _oidList?[_oidIndex] ?? ""; } /// /// 다음 OID로 이동 /// private void MoveToNextOid() { _oidIndex = (_oidIndex + 1) % (_oidList?.Length ?? 1); } /// /// 모듈 데이터 초기화 /// 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; } } /// /// 선반 통신 실패 확인 /// private void CheckShelfCommFail(ref DeviceSystemData systemData) { systemData.ShelfCommFail = systemData.StatusData.batteryStatus == 0x0003; } /// /// 평균 온도 계산 /// 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; } /// /// 트랜잭션 로그 /// private void LogTransaction(string message) { try { var logMessage = csLog.trx_msg_print(message, 1); OnSetResult?.Invoke(logMessage, false); } catch { // 로그 실패 시 무시 } } /// /// 오류 로그 /// 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 } }