# 5. OPC UA 서버 기능 (Phase 1) — 완료

This commit is contained in:
windpacer
2026-04-15 08:19:55 +00:00
parent 85e596d66b
commit d9f5bfd6f6
32 changed files with 1013 additions and 6 deletions

View File

@@ -7,6 +7,93 @@
## 완료된 작업
### 기능 추가 — OPC UA 서버 기능 (2026-04-15)
#### 배경
ExperionCrawler가 OPC UA 클라이언트 역할만 했으나, 외부 OPC UA 클라이언트(SCADA, MES 등)가 ExperionCrawler에 접속해 실시간 값을 읽을 수 있도록 OPC UA 서버 기능 추가.
#### 아키텍처
```
[Experion HS R530] ──(OPC UA Client)──► ExperionCrawler ◄──(OPC UA Client)── [외부 시스템]
(OPC UA Server)
[PostgreSQL DB]
```
#### 주소 공간 구조
```
Root/Objects/ExperionCrawler
├── ServerInfo/Status, PointCount, LastUpdateTime
└── Realtime/<tagname_1>, <tagname_2>, … (ns=2;s=tag_{tagname})
```
#### 수정/추가 파일
| 파일 | 수정 내용 |
|------|----------|
| `src/Web/ExperionCrawler.csproj` | `OPCFoundation.NetStandard.Opc.Ua.Server v1.5.378.134` 패키지 추가 |
| `src/Web/appsettings.json` | `OpcUaServer` 섹션 추가 (Port:4841, EnableSecurity:false, AllowAnonymous:true) |
| `src/Core/Application/Interfaces/IExperionServices.cs` | `IExperionOpcServerService` 인터페이스, `OpcServerStatus` record, `GetRealtimeNodeDataTypesAsync()` 추가 |
| `src/Infrastructure/OpcUa/ExperionOpcServerNodeManager.cs` | 신규 — `CustomNodeManager2` 상속, 주소 공간 관리 (`CreateAddressSpace`, `RebuildAddressSpace`, `UpdateNodeValue`) |
| `src/Infrastructure/OpcUa/ExperionOpcServerService.cs` | 신규 — `ExperionStandardServer` + `ExperionOpcServerService` (`IHostedService` + `IExperionOpcServerService`) |
| `src/Infrastructure/OpcUa/ExperionRealtimeService.cs` | `_pointCache` (nodeId→RealtimePoint) 추가; `FlushPendingAsync`에서 OPC 서버 노드 값 lazy 갱신 |
| `src/Infrastructure/Database/ExperionDbContext.cs` | `GetRealtimeNodeDataTypesAsync()` — realtime_table × node_map_master 조인 |
| `src/Web/Controllers/ExperionControllers.cs` | `ExperionOpcServerController` 추가 (start/stop/status/rebuild) |
| `src/Web/Program.cs` | `ExperionOpcServerService` Singleton+HostedService 등록 |
| `src/Web/wwwroot/index.html` | 08 OPC UA 서버 탭 + pane-opcsvr 섹션 추가 |
| `src/Web/wwwroot/js/app.js` | `srvLoad/Start/Stop/Rebuild/_srvRender/_srvStartPoll/_srvStopPoll` 구현 |
| `src/Web/wwwroot/css/style.css` | `.srv-status-card`, `.srv-meta`, `.dot.grn` 스타일 추가 |
#### 주요 설계 결정
| 항목 | 결정 |
|------|------|
| 인증서 | 기존 `pki/own/certs/{hostname}.pfx` 재사용 (`ApplicationType.ClientAndServer`) |
| 포트 | 기본 4841 (4840은 Experion HS R530이 사용 가능) |
| 보안 | 기본 None (appsettings.json에서 변경 가능) |
| 자동 재시작 | `opcserver_autostart.json` 플래그 파일 패턴 (RealtimeService와 동일) |
| 순환 참조 | `IServiceProvider` lazy resolve — `_opcServer ??= _sp.GetService<IExperionOpcServerService>()` |
| FlushLoop 연동 | 500ms 배치 DB 업데이트 후 → OPC 서버 노드 값도 동시 갱신 (DB 폴링 없음) |
#### API 엔드포인트
- `GET /api/opcserver/status` — 상태 조회 (running, clientCount, nodeCount, endpointUrl, startedAt)
- `POST /api/opcserver/start` — 서버 시작
- `POST /api/opcserver/stop` — 서버 중지
- `POST /api/opcserver/rebuild` — 주소 공간 재구성
#### 빌드 결과
- 경고 11건 (기존 8건 + OPC SDK Server Start/Stop deprecated 3건), **에러 0건** — 빌드 성공
#### OPC UA 서버가 노출하는 데이터
**데이터 출처**: `realtime_table`에 등록된 포인트 전체 (포인트빌더에서 빌드/수동 추가한 포인트)
**주소 공간 구조**
```
Root/Objects/ExperionCrawler
├── ServerInfo/
│ ├── Status (String) — "Running" / "Stopped"
│ ├── PointCount (Int32) — 구독 중인 포인트 수
│ └── LastUpdateTime (DateTime) — 마지막 값 갱신 시각
└── Realtime/
├── <tagname_1> ns=2;s=tag_FIC101_PV
├── <tagname_2>
└── …
```
**NodeId 명명 규칙**: `ns=2;s=tag_{tagname}`
**DataType 결정**: `realtime_table` × `node_map_master` 조인
- Double/Float/Int32/Int64/Boolean/DateTime → 해당 OPC UA 타입
- 기타/NULL → String (fallback)
**접근 제한**: 읽기 전용 (`AccessLevel = CurrentRead`), `Historizing = false`
**갱신 주기**: Experion HS R530 → FlushLoop 500ms 배치 → DB + OPC 서버 노드 동시 갱신
---
### 로그 정리 — 스냅샷 로그 2줄 → 1줄 (2026-04-15)
#### 증상