삽질하다 도저히 문제 파악이 안돼서 opcUaManager로 분리 테스트 중
This commit is contained in:
161
opcUaManager/Services/OpcCrawlerService.cs
Normal file
161
opcUaManager/Services/OpcCrawlerService.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
using Opc.Ua;
|
||||
using Opc.Ua.Client;
|
||||
using OpcUaManager.Models;
|
||||
|
||||
namespace OpcUaManager.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 원본 HoneywellCrawler 를 Service 로 분리.
|
||||
/// v1.5.374.70: BrowseNextAsync 가 튜플 반환이 아닌 BrowseNextResponse 객체 반환으로 변경됨.
|
||||
/// </summary>
|
||||
public class OpcCrawlerService
|
||||
{
|
||||
private readonly ILogger<OpcCrawlerService> _logger;
|
||||
private readonly OpcSessionService _sessionSvc;
|
||||
|
||||
public OpcCrawlerService(ILogger<OpcCrawlerService> logger, OpcSessionService sessionSvc)
|
||||
{
|
||||
_logger = logger;
|
||||
_sessionSvc = sessionSvc;
|
||||
}
|
||||
|
||||
public async Task<CrawlResult> CrawlAsync(CrawlRequest req)
|
||||
{
|
||||
if (!_sessionSvc.IsConnected)
|
||||
return new CrawlResult { Success = false, Message = "세션이 연결되어 있지 않습니다." };
|
||||
|
||||
var session = _sessionSvc.GetRawSession();
|
||||
if (session == null)
|
||||
return new CrawlResult { Success = false, Message = "Raw 세션을 가져올 수 없습니다." };
|
||||
|
||||
var tags = new List<TagMaster>();
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("저인망 탐사 시작: {NodeId}", req.StartNodeId);
|
||||
NodeId rootNode = NodeId.Parse(req.StartNodeId);
|
||||
await BrowseRecursiveAsync(session, rootNode, 0, req.MaxDepth, tags);
|
||||
|
||||
string csvPath = await SaveToCsvAsync(tags);
|
||||
|
||||
_logger.LogInformation("탐사 완료: {Count}개 노드", tags.Count);
|
||||
return new CrawlResult
|
||||
{
|
||||
Success = true,
|
||||
Message = $"탐사 완료: {tags.Count}개 노드 발견",
|
||||
TotalNodes = tags.Count,
|
||||
Tags = tags,
|
||||
CsvPath = csvPath
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Crawler 실행 오류");
|
||||
return new CrawlResult { Success = false, Message = ex.Message, Tags = tags };
|
||||
}
|
||||
}
|
||||
|
||||
private async Task BrowseRecursiveAsync(
|
||||
Session session, NodeId nodeId, int level, int maxDepth, List<TagMaster> tags)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 원본 동일: BrowseAsync 객체 방식
|
||||
BrowseDescription description = new()
|
||||
{
|
||||
NodeId = nodeId,
|
||||
BrowseDirection = BrowseDirection.Forward,
|
||||
ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences,
|
||||
IncludeSubtypes = true,
|
||||
NodeClassMask = (uint)(NodeClass.Variable | NodeClass.Object),
|
||||
ResultMask = (uint)BrowseResultMask.All
|
||||
};
|
||||
|
||||
BrowseResponse response = await session.BrowseAsync(
|
||||
null, null, 0, [description], default);
|
||||
|
||||
if (response?.Results == null || response.Results.Count == 0) return;
|
||||
|
||||
foreach (var result in response.Results)
|
||||
{
|
||||
await ProcessReferencesAsync(session, result.References, level, maxDepth, tags);
|
||||
|
||||
byte[] cp = result.ContinuationPoint;
|
||||
while (cp != null && cp.Length > 0)
|
||||
{
|
||||
// FIX CS8130/CS1503: BrowseNextAsync 튜플 방식 → BrowseNextResponse 객체 방식
|
||||
// v1.5.374.70 에서 반환 타입이 Task<BrowseNextResponse> 로 변경됨
|
||||
BrowseNextResponse nextResponse = await session.BrowseNextAsync(
|
||||
null,
|
||||
false,
|
||||
new ByteStringCollection { cp },
|
||||
default);
|
||||
|
||||
if (nextResponse?.Results != null && nextResponse.Results.Count > 0)
|
||||
{
|
||||
var nextResult = nextResponse.Results[0];
|
||||
await ProcessReferencesAsync(session, nextResult.References, level, maxDepth, tags);
|
||||
cp = nextResult.ContinuationPoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 원본과 동일: 특정 노드 권한 에러 무시
|
||||
_logger.LogDebug("노드 탐색 건너뜀 [{Level}] {NodeId}: {Msg}", level, nodeId, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessReferencesAsync(
|
||||
Session session,
|
||||
ReferenceDescriptionCollection? references,
|
||||
int level,
|
||||
int maxDepth,
|
||||
List<TagMaster> tags)
|
||||
{
|
||||
if (references == null || references.Count == 0) return;
|
||||
|
||||
foreach (var rd in references)
|
||||
{
|
||||
NodeId childId = ExpandedNodeId.ToNodeId(rd.NodeId, session.NamespaceUris);
|
||||
|
||||
tags.Add(new TagMaster
|
||||
{
|
||||
TagName = rd.BrowseName.Name ?? "Unknown",
|
||||
FullNodeId = childId.ToString(),
|
||||
NodeClass = rd.NodeClass.ToString(),
|
||||
Level = level
|
||||
});
|
||||
|
||||
_logger.LogDebug("{Indent}[{Class}] {Name} ({Id})",
|
||||
new string(' ', level * 2), rd.NodeClass, rd.BrowseName.Name, childId);
|
||||
|
||||
if (rd.NodeClass == NodeClass.Object && level < maxDepth)
|
||||
await BrowseRecursiveAsync(session, childId, level + 1, maxDepth, tags);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> SaveToCsvAsync(List<TagMaster> tags)
|
||||
{
|
||||
string path = Path.GetFullPath("Honeywell_FullMap.csv");
|
||||
try
|
||||
{
|
||||
await using var sw = new StreamWriter(path);
|
||||
await sw.WriteLineAsync("Level,Class,Name,NodeId");
|
||||
foreach (var tag in tags)
|
||||
await sw.WriteLineAsync($"{tag.Level},{tag.NodeClass},{tag.TagName},{tag.FullNodeId}");
|
||||
|
||||
_logger.LogInformation("CSV 저장 완료: {Path}", path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "CSV 저장 실패");
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user