- P&ID: 연결 분석 API, Prefix 규칙 관리, 카테고리 분류, DXF 그래프 빌드 - LLM: 대화 요약, tool card 영구 보존, 시계열 차트(uPlot), 에이전트 모드 - KB: 청크 미리보기, Field Instrument Inference, 인증/Qdrant 클라이언트 - MCP: 서버 기능 확장, 파이프라인 수정, timeout 개선 - Frontend: P&ID UI, LLM UI, KB UI, OPC UA Write 탭 추가 - 설정: AGENTS.md, plant_context, README, opencode.json 업데이트 - 정리: 진단 체크리스트 문서 삭제
11 KiB
11 KiB
ExperionCrawler — OPC UA Write 기능 추가 구현 계획
⚠️ 이 문서를 실행할 LLM에게
이 문서는 ExperionCrawler 프로젝트에 OPC UA Write 기능(SP/OP/MD/MODE 쓰기)을 추가하는 구현 계획이다. 아래 규칙을 반드시 준수하여 구현할 것:
- diagnosis-checklist.md 규칙 준수: STEP 1~8을 순서대로 실행. 코드를 읽지 않고 구현하지 말 것.
- JSON camelCase 규칙: 모든 Controller
Ok(...)응답은 명시적 camelCase 키 사용. 예:return Ok(new { success = r.Success, nodeId = r.NodeId, status = r.Status, error = r.Error });- 네임스페이스:
ExperionCrawler.Infrastructure.OpcUa(Infrastructure),ExperionCrawler.Core.Application.Interfaces(인터페이스),ExperionCrawler.Core.Application.DTOs(DTO)- 단위별 구현: 각 TODO 항목을 하나씩 완료하고, 검증 단계를 실행한 후 다음으로 진행.
- 빌드 검증: 모든 코드 변경 후
dotnet build src/Web/ExperionCrawler.csproj로 빌드 확인.- 기존 코드 수정 금지:
ExperionOpcClient.cs(읽기 전용)는 수정하지 않음. 신규 파일로 추가.- DI 등록: Program.cs에 신규 서비스를 Scoped로 등록.
- 완료 표시: 각 TODO 항목 완료 시
[ ]→[x]로 변경하고, 검증 결과 기록.- 프론트엔드:
wwwroot/js/app.js에 Write 탭 UI 추가. 기존 패턴(tab navigation,api()helper,log()helper) 따름.- 보안: 쓰기 이력 로깅은 DB가 아닌 Application Log로만 기록 (단순화).
테스트 결과 요약 (TEST_RESULTS.md 기반)
| 태그 | 노드 ID | DataType | 방법 | 결과 |
|---|---|---|---|---|
| SP | ns=1;s=sinamserver:ficq-6101.sp |
float | WriteAsync() 직접 |
✅ 성공 |
| OP | ns=1;s=sinamserver:ficq-6101.op |
Double | WriteAsync() (MD=MAN 필요) |
✅ 성공 |
| MD | ns=1;s=sinamserver:ficq-6101.md |
EnumValueType | EnumValueType Write |
✅ 성공 |
| Mode | ns=1;s=sinamserver:ficq-6101.mode |
EnumValueType | EnumValueType Write |
✅ 성공 |
핵심 발견
- MD/Mode는 Method가 아님:
EnumValueType(Value, DisplayName, Description)을WriteAsync()로 직접 쓰기 - OP 쓰기 조건: MD를 MAN(0)으로 변경해야 OP 쓰기 가능 (AUTO면
BadNoData) - SDK v1.5.378:
WriteValue(WriteValueId 아님),DefaultSessionFactory.CreateAsync()사용
구현 TODO 리스트
Phase 1: Backend Core (Infrastructure + Interface + DTO)
TODO 1-1: IExperionOpcWriteClient 인터페이스 정의
- 파일:
src/Core/Application/Interfaces/IExperionServices.cs - 작업: 기존 파일 하단에 인터페이스 추가
- 내용:
public interface IExperionOpcWriteClient { Task<ExperionWriteResultDto> WriteTagAsync(ExperionServerConfig cfg, string nodeId, double value, CancellationToken ct = default); Task<ExperionModeResultDto> SetModeAsync(ExperionServerConfig cfg, string nodeId, string mode, CancellationToken ct = default); } - 검증:
dotnet build성공
TODO 1-2: Write Result DTOs 정의
- 파일:
src/Core/Application/DTOs/ExperionDtos.cs - 작업: 파일 하단에 DTO 클래스 추가
- 내용:
public class ExperionWriteResultDto { public bool Success { get; set; } public string NodeId { get; set; } = ""; public string Status { get; set; } = ""; public string? Error { get; set; } } public class ExperionModeResultDto { public bool Success { get; set; } public string NodeId { get; set; } = ""; public string Mode { get; set; } = ""; public int EnumValue { get; set; } public string Status { get; set; } = ""; public string? Error { get; set; } } - 검증:
dotnet build성공
TODO 1-3: ExperionOpcWriteClient 구현체 생성
- 파일:
src/Infrastructure/OpcUa/ExperionOpcWriteClient.cs(신규) - 작업:
/tmp/opcua-test/ExperionOpcWriteClient.cs를 기반으로 구현. 변경 사항:- 네임스페이스:
ExperionCrawler.Infrastructure.OpcUa IExperionOpcWriteClient구현IOpcUaConfigProvider의존성 주입 (기존ExperionOpcClient와 동일 패턴)BuildConfigAsync,SelectEndpointAsync,CreateSessionAsync는ExperionOpcClient와 동일한 로직 사용ExperionServerConfig사용 (별도 파라미터 아님)- Result 타입:
ExperionWriteResultDto,ExperionModeResultDto(Core DTO 사용)
- 네임스페이스:
- 검증:
dotnet build성공
TODO 1-4: Program.cs에 DI 등록
- 파일:
src/Web/Program.cs - 작업:
IExperionOpcClient등록 바로 아래에IExperionOpcWriteClient등록 추가builder.Services.AddScoped<IExperionOpcWriteClient, ExperionOpcWriteClient>(); - 검증:
dotnet build성공
Phase 2: API Controller
TODO 2-1: Write Controller 생성
- 파일:
src/Web/Controllers/ExperionControllers.cs - 작업: 파일 하단에 신규 Controller 추가
- 내용:
[ApiController] [Route("api/points")] public class ExperionWriteController : ControllerBase { private readonly IExperionOpcWriteClient _writeClient; private readonly IExperionOpcClient _readClient; private readonly IConfiguration _config; private readonly ILogger<ExperionWriteController> _logger; // ... } - 엔드포인트:
POST /api/points/write— SP/OP 쓰기POST /api/points/mode— MD/MODE 변경POST /api/points/control— 통합 제어 (MD→MAN → OP 쓰기 → AUTO 복귀)POST /api/points/read— 태그 현재값 읽기 (확인용)
- Request DTOs (Controller 내부에 정의):
public class WriteTagRequest(string NodeId, double Value); public class SetModeRequest(string NodeId, string Mode); public class ControlOpRequest(string TagName, double OpValue, bool? RestoreAuto = true); - ServerConfig:
appsettings.json의Experion:Default섹션에서 읽음 (기존 패턴 동일) - camelCase 응답: 모든
Ok(new { success = ..., nodeId = ..., status = ..., error = ... }) - 검증:
dotnet build성공
Phase 3: Frontend UI
TODO 3-1: Write 탭 HTML 추가
- 파일:
src/Web/wwwroot/index.html - 작업:
- nav-item 추가:
<a class="nav-item" data-tab="write">Write</a> - pane-write 추가: SP/OP/Mode 입력 폼 + 로그 영역
- nav-item 추가:
- UI 구성:
- 태그명 입력 (예:
ficq-6101) - 속성 선택: SP / OP / MD / Mode
- 값 입력 (SP/OP: 숫자, MD/Mode: MAN/AUTO 선택)
- 실행 버튼
- 통합 제어: "OP 변경 (자동 MAN→AUTO)" 체크박스
- 로그 출력 영역
- 태그명 입력 (예:
- 검증: 브라우저에서 탭 전환 확인
TODO 3-2: Write 탭 JS 구현
- 파일:
src/Web/wwwroot/js/app.js - 작업: 파일 하단에 Write 관련 함수 추가
- 함수:
writeTag()—/api/points/write호출setMode()—/api/points/mode호출controlOp()—/api/points/control호출readTagValue()—/api/points/read호출
- 검증: 브라우저에서 Write 탭에서 SP 쓰기 테스트
Phase 4: 검증 및 테스트
TODO 4-1: 빌드 검증
dotnet build src/Web/ExperionCrawler.csproj— 오류 없음dotnet test— 기존 테스트 통과
TODO 4-2: API 테스트 (Postman/curl)
POST /api/points/write— ficq-6101.sp = 36.0 → 성공POST /api/points/mode— ficq-6101.md = MAN → 성공POST /api/points/control— ficq-6101 OP = 35.5 → MAN 전환 → OP 쓰기 → AUTO 복귀 → 성공POST /api/points/read— ficq-6101.sp → 현재값 반환
TODO 4-3: Frontend 테스트
- Write 탭에서 SP 쓰기 UI 테스트
- Mode 변경 UI 테스트
- 통합 제어 UI 테스트
파일 변경 요약
| 파일 | 액션 | 내용 |
|---|---|---|
src/Core/Application/Interfaces/IExperionServices.cs |
수정 | IExperionOpcWriteClient 인터페이스 추가 |
src/Core/Application/DTOs/ExperionDtos.cs |
수정 | ExperionWriteResultDto, ExperionModeResultDto 추가 |
src/Infrastructure/OpcUa/ExperionOpcWriteClient.cs |
신규 | Write 클라이언트 구현체 |
src/Web/Program.cs |
수정 | DI 등록 1줄 추가 |
src/Web/Controllers/ExperionControllers.cs |
수정 | ExperionWriteController + Request DTOs 추가 |
src/Web/wwwroot/index.html |
수정 | Write 탭 HTML 추가 |
src/Web/wwwroot/js/app.js |
수정 | Write 탭 JS 함수 추가 |
구현 진행 상태
| # | TODO | 상태 | 검증 | 비고 |
|---|---|---|---|---|
| 1-1 | IExperionOpcWriteClient 인터페이스 | ✅ 완료 | 빌드 성공 | IExperionServices.cs 하단 추가 |
| 1-2 | Write Result DTOs | ✅ 완료 | 빌드 성공 | ExperionDtos.cs 하단 추가 |
| 1-3 | ExperionOpcWriteClient 구현체 | ✅ 완료 | 빌드 성공 | IOpcUaConfigProvider DI 사용 |
| 1-4 | Program.cs DI 등록 | ✅ 완료 | 빌드 성공 | Scoped 등록 |
| 2-1 | Write Controller | ✅ 완료 | 빌드 성공 | 4개 엔드포인트 + Request DTOs |
| 3-1 | Write 탭 HTML | ✅ 완료 | 시각 확인 | nav-item 15 + pane-write |
| 3-2 | Write 탭 JS | ✅ 완료 | 시각 확인 | wrWriteTag/wrSetMode/wrControlOp/wrReadTag |
| 4-1 | 빌드 검증 | ✅ 완료 | 0 Warning, 0 Error | dotnet build 성공 |
| 4-2 | API 테스트 | ⬜ 미완료 | 런타임 테스트 필요 | |
| 4-3 | Frontend 테스트 | ⬜ 미완료 | 브라우저 테스트 필요 |
참고: 기존 아키텍처 패턴
Service 등록 패턴 (Program.cs)
builder.Services.AddScoped<IExperionOpcClient, ExperionOpcClient>();
// ↑ 이 패턴을 따름
Controller에서 ServerConfig 읽기 패턴
var section = _config.GetSection("Experion:Default");
return new ExperionServerConfig
{
ServerHostName = section["ServerHostName"] ?? "",
Port = section.GetValue<int?>("Port") ?? 4840,
ClientHostName = section["ClientHostName"] ?? "dbsvr",
UserName = section["UserName"] ?? "",
Password = section["Password"] ?? ""
};
JSON camelCase 응답 패턴
return Ok(new { success = r.Success, nodeId = r.NodeId, status = r.Status, error = r.Error });
Frontend api() helper
async function api(method, path, body) {
const opt = { method, headers: { 'Content-Type': 'application/json' } };
if (body) opt.body = JSON.stringify(body);
const res = await fetch(path, opt);
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
return res.json();
}