온도프로파일/PV일관성/PointBuilder/history 작업지시, 신호태그·스팀유량 진단, 베이직아키텍처 재설계, MSDS, LLM채팅 구조 등. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
911 lines
34 KiB
Markdown
911 lines
34 KiB
Markdown
# 베이직 아키텍처 · 태그 디자인 전면 재설계
|
|
|
|
> 본 문서는 HC900-AX 프로젝트의 전면 재설계 결과를 정의한다.
|
|
> 2026-06-08, 전면 개정.
|
|
|
|
---
|
|
|
|
## 목차
|
|
|
|
1. [핵심 원칙](#1-핵심-원칙)
|
|
2. [HC900 Modbus 통신 규칙](#2-hc900-modbus-통신-규칙)
|
|
3. [Sinam_Tag_all.xlsx 데이터 구조](#3-sinam_tag_allxlsx-데이터-구조)
|
|
4. [주소 변환 규칙 (공식 검증 완료)](#4-주소-변환-규칙-공식-검증-완료)
|
|
5. [태그명 체계 및 등록 규칙](#5-태그명-체계-및-등록-규칙)
|
|
6. [register-map-cN.json 포맷](#6-register-map-cnjson-포맷)
|
|
7. [아카이브/모니터링 제어](#7-아카이브모니터링-제어)
|
|
8. [컨트롤러별 독립 맵 전략](#8-컨트롤러별-독립-맵-전략)
|
|
9. [데이터 흐름 (전면 개정)](#9-데이터-흐름-전면-개정)
|
|
10. [build_register_map_from_sinam.py 처리 규칙](#10-build_register_map_from_sinampy-처리-규칙)
|
|
11. [C# 서비스 수정 사항](#11-c-서비스-수정-사항)
|
|
12. [tag_metadata 적재](#12-tag_metadata-적재)
|
|
13. [알려진 문제](#13-알려진-문제)
|
|
14. [폐기 항목](#14-폐기-항목)
|
|
15. [마이그레이션 순서](#15-마이그레이션-순서)
|
|
|
|
---
|
|
|
|
## 1. 핵심 원칙
|
|
|
|
### 1.1 단일 진실 공급원 (Single Source of Truth)
|
|
- **`docs/Sinam_Tag_all.xlsx`** 가 모든 태그 정보의 유일한 출처
|
|
- HC Designer CSV (`SignalTags.csv`, `Variables.csv`, `SummaryFunctionBlockReport.csv`)는 **주소 검증용 보조 자료**
|
|
- Experion OPC UA legacy (소문자 태그명, node_id 등)는 **완전히 폐기**
|
|
|
|
### 1.2 태그명 표기법
|
|
- **모든 태그명은 대문자** (OPC UA 강제 소문자 규칙 폐기)
|
|
- 구분자: `-` (하이픈) + `.` (파라미터)
|
|
- 예: `FICQ-6101.PV`, `P-9114.STATE`, `LI-9100.PV`
|
|
- Experion ItemName 기준 (`FICQ-6101`), HC900 네이티브명(`FIQ6101`) 사용 안 함
|
|
|
|
### 1.3 컨트롤러별 독립 맵
|
|
- 각 HC900 컨트롤러(C1~C4)는 **독립적인 `register-map-cN.json`** 사용
|
|
- `config/gateway-config.json`에서 각 컨트롤러의 enabled/disabled 관리
|
|
- 웹 UI Setup 페이지에서 컨트롤러 추가/삭제/시작/중지 가능
|
|
|
|
### 1.4 레지스터 맵 엔트리 설계 원칙
|
|
- **❌ Loop 블록 통째로(192 regs) 등록하지 않음** — 게이트웨이의 배치 그룹핑이 개별 엔트리를 쪼갤 수 없으므로 `count > 120` 인 단일 엔트리는 단일 FC03 호출로 192 regs 읽기를 시도하여 HC900에서 실패
|
|
- **✅ 각 파라미터를 개별 엔트리로 등록** (PV=2regs, SP=2regs, OP=2regs, MODE=1reg, ...)
|
|
- 게이트웨이가 주소 순서로 정렬 후 인접 엔트리들을 동적으로 ≤120 배치로 묶어서 FC03 호출
|
|
|
|
---
|
|
|
|
## 2. HC900 Modbus 통신 규칙
|
|
|
|
### 2.1 기본 프로토콜
|
|
|
|
| 항목 | 값 | 근거 |
|
|
|------|-----|------|
|
|
| 전송 | Modbus TCP | `modbus_tcp.cpp` |
|
|
| HC900 IP | 컨트롤러별 설정 | `gateway-config.json` |
|
|
| 포트 | 502 | 기본값 |
|
|
| Unit ID | 1 | `modbus_tcp.cpp:13` |
|
|
| 소켓 timeout | 2초 | `modbus_tcp.cpp:14` |
|
|
| Watchdog | 5초 무응답 → Reconnecting | `modbus_tcp.cpp:15` |
|
|
| Max retry | 5회 → Fault (30초 후 자동 재시도) | `modbus_tcp.cpp:16` |
|
|
| Float format | **FP B** (IEEE 754 BigEndian, HighFirst, Normal) | HC900 매뉴얼, `vendor_formats.hpp` |
|
|
|
|
### 2.2 Function Codes
|
|
|
|
| 기능 | FC | 방향 | 구현 |
|
|
|------|----|------|------|
|
|
| Read Holding Registers | **FC03** | HC900 → Gateway | `modbus_tcp.cpp:171` |
|
|
| Write Multiple Registers | **FC16** (0x10) | Gateway → HC900 | `modbus_tcp.cpp:251` |
|
|
|
|
### 2.3 배치 읽기 규칙 (C++ 게이트웨이 자동 수행)
|
|
|
|
```
|
|
MAX_BATCH = 120 연속 레지스터 (gateway.cpp:126)
|
|
```
|
|
|
|
게이트웨이의 `ReadAllRegisters()`는 런타임에 register-map 엔트리들을 주소 순서로 정렬한 후, 동일한 120-register 윈도우 안에 들어가는 엔트리들을 한 번의 FC03로 묶어서 읽음:
|
|
|
|
```cpp
|
|
// gateway.cpp:124-174 — 동적 배치 그룹핑
|
|
while (i < sorted_indices_.size()) {
|
|
uint32_t batch_start = registers_[sorted_indices_[i]].addr;
|
|
size_t j = i;
|
|
while (j < sorted_indices_.size()) {
|
|
const auto& e = registers_[sorted_indices_[j]];
|
|
if (e.addr + e.count - batch_start > MAX_BATCH) break;
|
|
++j;
|
|
}
|
|
// FC03 read_raw(batch_start, read_count)
|
|
// 각 엔트리를 버퍼에서 개별 decode
|
|
i = j;
|
|
}
|
|
```
|
|
|
|
**중요:** 개별 엔트리의 `addr + count`가 120을 넘으면 단일 FC03로 처리되므로, 192-regs 통째 등록은 불가능. 각 파라미터는 count=1(uint16) 또는 2(float32)로 등록해야 함.
|
|
|
|
### 2.4 Loop 블록 구조 (매뉴얼 Table 6-3)
|
|
|
|
각 PID 루프는 256개 레지스터 블록 (루프 블록 크기 = 192 regs, 0x40~0xFF).
|
|
|
|
| Offset | 파라미터 | 타입 | 본문 접근 |
|
|
|--------|---------|------|:---------:|
|
|
| 0x00 | PV | float32 | R |
|
|
| 0x02 | Remote SP (SP2) | float32 | R |
|
|
| 0x04 | **Working Set Point (SP)** | float32 | RW |
|
|
| 0x06 | **Output (OP)** | float32 | RW |
|
|
| 0x0C | Gain #1 | float32 | RW |
|
|
| 0x0E | Direction | float32 | R |
|
|
| 0x10 | Reset #1 | float32 | RW |
|
|
| 0x12 | Rate #1 | float32 | RW |
|
|
| 0x14 | Cycle Time #1 | float32 | R |
|
|
| 0x16 | PV Low Range | float32 | R |
|
|
| 0x18 | PV High Range | float32 | R |
|
|
| 0x1A | Alarm #1 SP #1 (AL1SP1) | float32 | RW |
|
|
| 0x1C | Alarm #1 SP #2 (AL1SP2) | float32 | RW |
|
|
| 0x20 | Gain #2 | float32 | RW |
|
|
| 0x2A | Local SP #1 (LSP1) | float32 | RW |
|
|
| 0x2C | Local SP #2 (LSP2) | float32 | RW |
|
|
| 0x2E | Alarm #2 SP #1 (AL2SP1) | float32 | RW |
|
|
| 0x30 | Alarm #2 SP #2 (AL2SP2) | float32 | RW |
|
|
| 0x34 | SP Low Limit | float32 | RW |
|
|
| 0x36 | SP High Limit | float32 | RW |
|
|
| 0x3A | Output Low Limit | float32 | RW |
|
|
| 0x3C | Output High Limit | float32 | RW |
|
|
| 0x3E | Working Output (OPWORK) | float32 | RW |
|
|
| 0x46 | Ratio | float32 | RW |
|
|
| 0x48 | Bias | float32 | RW |
|
|
| 0x4A | Deviation | float32 | R |
|
|
| 0x4E | Manual Reset | float32 | RW |
|
|
| 0x50 | Feedforward Gain | float32 | RW |
|
|
| 0x52 | Local %CO | float32 | RW |
|
|
| 0xB7 | Fuzzy Enable | uint16 | RW |
|
|
| 0xB8 | Autotune Request | uint16 | RW |
|
|
| 0xB9 | Anti-soot Enable | uint16 | RW |
|
|
| **0xBA** | **Auto/Manual State (MODE write target)** | uint16 | RW |
|
|
| 0xBB | SP Select State | uint16 | RW |
|
|
| 0xBC | Remote/Local SP State | uint16 | RW |
|
|
| **0xBE** | **Loop Status (MODE read source)** | uint16 | R |
|
|
|
|
### 2.5 루프 주소 계산
|
|
|
|
```
|
|
Loop 1~24: base = 0x0040 + (N-1) * 0x0100
|
|
Loop 25~32: base = 0x7840 + (N-25) * 0x0100
|
|
```
|
|
|
|
검증 완료 (SummaryFunctionBlockReport.csv):
|
|
|
|
| Loop | CSV 주소 | 계산 | 일치 |
|
|
|:----:|:--------:|------|:----:|
|
|
| #1 | 0x0040 | `0x0040 + 0*0x0100` | ✅ |
|
|
| #11 | 0x0A40 | `0x0040 + 10*0x0100` | ✅ |
|
|
| #18 | 0x1140 | `0x0040 + 17*0x0100` | ✅ |
|
|
| #24 | 0x1740 | `0x0040 + 23*0x0100` | ✅ |
|
|
| #25 | 0x7840 | `0x7840 + 0*0x0100` | ✅ |
|
|
| #32 | 0x7F40 | `0x7840 + 7*0x0100` | ✅ |
|
|
|
|
### 2.6 주소 영역
|
|
|
|
| 영역 | 시작 | 끝 | 설명 |
|
|
|------|:----:|:---:|------|
|
|
| Misc | 0x0000 | 0x003F | 기타 파라미터 |
|
|
| Loop 1~24 | 0x0040 | 0x15FF | 루프 블록 (각 256 regs) |
|
|
| Loop 25~32 | 0x7840 | 0x78FF | 루프 블록 확장 |
|
|
| Analog Input | 0x1800 | 0x187F | AI 값 (Function Code 03) |
|
|
| **Variable** | **0x18C0** | **0x1D6F** | R/W 변수 (MATH_VAR) |
|
|
| **Signal Tag** | **0x2000** | **0x27CF** | R 태그 (TAG) |
|
|
|
|
---
|
|
|
|
## 3. Sinam_Tag_all.xlsx 데이터 구조
|
|
|
|
### 3.1 파일 개요
|
|
- 1개 시트 (Sheet1)
|
|
- **3407 데이터 행**, 150 컬럼
|
|
- 다중 섹션 구조
|
|
|
|
### 3.2 Section 구성
|
|
|
|
| Section | 행 범위 | 헤더 첫 컬럼 | 설명 |
|
|
|---------|---------|-------------|------|
|
|
| 1 (ItemName) | Row 2~ | `ItemName` | 메인 태그 정의 (AnalogPoint, StatusPoint, HistoryParameters) |
|
|
| 2+ (ParentItemName) | Row 1163~4424 | `ParentItemName` | FlexibleParameters 확장 속성 |
|
|
|
|
### 3.3 Section 1: ItemName — 주요 컬럼
|
|
|
|
| 컬럼명 | 0-indexed | 예시 | 설명 |
|
|
|--------|:--------:|------|------|
|
|
| `ItemName` | 0 | `FICQ-6101` | Experion 태그명 (대문자) |
|
|
| `Class` | 1 | `AnalogPoint` / `StatusPoint` / `HistoryParameters` | 태그 클래스 |
|
|
| `ItemDescription` | 5 | `FICQ-6101 PV` | 설명 |
|
|
| `DownloadedName` | 6 | `FICQ-6101` | 다운로드명 |
|
|
| `AreaCode` | 11 | `P6` | 영역 코드 |
|
|
| `TrendParameter` | 18 | `True` / `PV` / `False` | Experion 트렌드 대상 (참고용, 우리 시스템과 무관) |
|
|
| `SourceAddressPV` | 29 | `C3 LOOP 11 PV` / `C4 TAG 32 VALUE` | PV의 HC900 주소 |
|
|
| `SourceAddressOP` | 30 | `C3 LOOP 11 OPWORK` / `C4 MATH_VAR 5 VALUE` | OP의 HC900 주소 |
|
|
| `SourceAddressMD` | 31 | `C3 LOOP 11 LOOPSTAT` | MODE의 HC900 주소 |
|
|
| `DescriptorState0~7` | 61~68 | `RUN`, `STOP`, `FAULT` | 상태 레이블 (StatusPoint 한정) |
|
|
| `Units` | (AnalogPoint) | `kg/h` | 엔지니어링 단위 |
|
|
| `RangeLow` / `RangeHigh` | (AnalogPoint) | `0` / `100` | 측정 범위 |
|
|
|
|
### 3.4 Section 2+: ParentItemName (FlexibleParameters)
|
|
|
|
| 컬럼명 | 0-indexed | 예시 | 설명 |
|
|
|--------|:--------:|------|------|
|
|
| `ParentItemName` | 0 | `FICQ-6101` | 부모 태그명 |
|
|
| `Class` | 1 | `FlexibleParameters` | 고정 |
|
|
| `ParamName` | 2 | `QV`, `RST`, `STATE`, `AL1SP1`, `HZ` | 확장 파라미터명 |
|
|
| `TypeDatabaseReference` | 5 | `16 bit signed integer (INT2)` | 데이터타입 |
|
|
| `ScanPeriodPVUDSP` | 25 | `2` | 스캔 주기 (초) |
|
|
| `UnitsUDSP` | 26 | `kg` | 단위 |
|
|
| `RangeHighUDSP` / `RangeLowUDSP` | 27/28 | 범위 |
|
|
| `StatusNumStatesUDSP` | 32 | `2` | 상태 개수 |
|
|
| `DescriptorState0~7UDSP` | 33~40 | `OFF`, `ON` | 상태 레이블 (Status 한정) |
|
|
| `DestinationAddressPVUDSP` | 23 | `C3 MATH_VAR 37 VALUE` | 쓰기 주소 (있으면 RW, 없으면 R) |
|
|
|
|
### 3.5 SourceAddress 형식
|
|
|
|
| 형식 | 예시 | 변환 규칙 |
|
|
|------|------|----------|
|
|
| `C{N} TAG {N} VALUE` | `C4 TAG 32 VALUE` | `addr = 0x2000 + (N-1)*2`, access=R |
|
|
| `C{N} MATH_VAR {N} VALUE` | `C4 MATH_VAR 5 VALUE` | `addr = 0x18C0 + (N-1)*2`, access=RW |
|
|
| `C{N} LOOP {N} {PARAM}` | `C3 LOOP 11 PV` | 루프 블록 내 파라미터 오프셋 |
|
|
| `C{N} LOOPX {N} {PARAM}` | `C3 LOOPX 25 WSP` | LOOPX = Loop 25~32와 동일 주소 체계 |
|
|
|
|
---
|
|
|
|
## 4. 주소 변환 규칙 (공식 검증 완료)
|
|
|
|
Sinam_Tag_all.xlsx의 SourceAddress 문자열을 HC900 Modbus 주소로 변환하는 공식:
|
|
|
|
### 4.1 TAG N → Modbus 주소
|
|
```
|
|
addr = 0x2000 + (N-1) * 2
|
|
```
|
|
검증: CSV `TAG 4 = FIQ6101 @ 0x2006` ✅
|
|
|
|
### 4.2 MATH_VAR N → Modbus 주소
|
|
```
|
|
addr = 0x18C0 + (N-1) * 2
|
|
```
|
|
검증: CSV `VAR 2 = LT3211_LSET @ 0x18C2` ✅
|
|
|
|
### 4.3 LOOP N PARAM → Modbus 주소
|
|
```
|
|
base = loop_base(N)
|
|
off = MNEMONIC_OFFSET[PARAM]
|
|
addr = base + off
|
|
```
|
|
검증: `C2 LOOP 18 WSP → base=0x1140, off=0x04 → addr=0x1144` ✅
|
|
|
|
### 4.4 Mnemonic → Offset 매핑
|
|
|
|
| Mnemonic | Offset | Experion Attr |
|
|
|----------|:------:|:-------------:|
|
|
| PV | 0x00 | PV |
|
|
| RSP / SP2 | 0x02 | — |
|
|
| WSP / SPWORK | 0x04 | **SP** |
|
|
| OP / OPWORK | 0x06 | **OP** |
|
|
| GAIN1 / PROP1 | 0x0C | GAIN |
|
|
| DIR | 0x0E | — |
|
|
| RESET1 | 0x10 | RESET |
|
|
| RATE1 | 0x12 | RATE |
|
|
| PVLOW | 0x16 | — |
|
|
| PVHIGH | 0x18 | — |
|
|
| AL1SP1 | 0x1A | — |
|
|
| AL1SP2 | 0x1C | — |
|
|
| LSP1 | 0x2A | — |
|
|
| LSP2 | 0x2C | — |
|
|
| AL2SP1 | 0x2E | — |
|
|
| SPLOW | 0x34 | SP_LO |
|
|
| SPHIGH | 0x36 | SP_HI |
|
|
| OPLOW | 0x3A | OP_LO |
|
|
| OPHIGH | 0x3C | OP_HI |
|
|
| OPWORK | 0x3E | OP |
|
|
| DEV | 0x4A | — |
|
|
| LOOPSTAT | **0xBE** | **MD (읽기)** |
|
|
| MODEIN | **0xBA** | **MD (쓰기)** |
|
|
| AMSTAT | 0xBA | — |
|
|
|
|
**⚠️ MODE 주의:** 읽기는 LOOPSTAT(0xBE), 쓰기는 Auto/Man State(0xBA). `build_register_map_from_sinam.py` 초기 버전에서 write_addr이 0xBE로 잘못 설정된 버그 있음 → 수정 완료.
|
|
|
|
---
|
|
|
|
## 5. 태그명 체계 및 등록 규칙
|
|
|
|
### 5.1 태그명 포맷
|
|
|
|
```
|
|
{Experion-ItemName}.{Parameter}
|
|
```
|
|
|
|
| 유형 | 태그명 예시 | 출처 |
|
|
|------|-----------|------|
|
|
| Loop PV | `FICQ-6101.PV` | ItemName + Mnemonic |
|
|
| Loop SP | `FICQ-6101.SP` | WSP/SPWORK → SP |
|
|
| Loop OP | `FICQ-6101.OP` | OP/OPWORK → OP |
|
|
| Loop MD | `FICQ-6101.MD` | LOOPSTAT(읽기)/MODEIN(쓰기) → MD |
|
|
| Loop GAIN | `FICQ-6101.GAIN` | GAIN1 → GAIN |
|
|
| Loop RESET | `FICQ-6101.RESET` | RESET1 → RESET |
|
|
| Loop RATE | `FICQ-6101.RATE` | RATE1 → RATE |
|
|
| Loop 기타 | `FICQ-6101.Deviation` | HC900명 유지 |
|
|
| AnalogPoint (TAG source) | `LI-9100` | Class=AnalogPoint, PV source=TAG |
|
|
| StatusPoint PV (TAG) | `P-9114` | Class=StatusPoint, PV source=TAG |
|
|
| StatusPoint OP (MATH_VAR) | `P-9114.OP` | Class=StatusPoint, OP source=MATH_VAR |
|
|
| FlexibleParameter | `FICQ-6101.QV` / `P-9114.STATE` | ParentItemName.ParamName |
|
|
|
|
### 5.2 Loop 파라미터 Experion명 매핑
|
|
|
|
Loop 블록 내 모든 파라미터 중 Experion이 노출하는 것만 Experion 속성명 사용, 나머지는 HC900 네이티브명 유지:
|
|
|
|
```
|
|
LOOP_LAYOUT 오프셋 → EXPERION_ATTR 매핑:
|
|
0x00(PV) → .PV
|
|
0x04(WSP) → .SP
|
|
0x06(OP) → .OP
|
|
0x0C(GAIN1) → .GAIN
|
|
0x10(RESET1) → .RESET
|
|
0x12(RATE1) → .RATE
|
|
0xBE(LOOPSTAT) → .MD
|
|
그 외 → HC900명 유지 (예: .Deviation, .PV_LowRange)
|
|
```
|
|
|
|
### 5.3 FlexibleParameter 등록
|
|
|
|
Section 2+에서 `ParentItemName.ParamName` 으로 등록:
|
|
|
|
| FlexibleParameter | 등록명 | 비고 |
|
|
|------------------|--------|------|
|
|
| FICQ-6101 / QV | `FICQ-6101.QV` | Quality Value (적산) |
|
|
| FICQ-6101 / RST | `FICQ-6101.RST` | Reset |
|
|
| FICQ-6101 / AL1SP1 | `FICQ-6101.AL1SP1` | Alarm Setpoint |
|
|
| P-9114 / STATE | `P-9114.STATE` | 운전 상태 |
|
|
| P-9114 / HZ | `P-9114.HZ` | 주파수 |
|
|
| P-9114 / HZSET | `P-9114.HZSET` | 주파수 설정 |
|
|
|
|
---
|
|
|
|
## 6. register-map-cN.json 포맷
|
|
|
|
### 6.1 엔트리 구조
|
|
|
|
```json
|
|
{
|
|
"tag": "FICQ-6101.PV",
|
|
"addr": 2624,
|
|
"write_addr": 2624,
|
|
"count": 2,
|
|
"type": "float32",
|
|
"access": "R",
|
|
"description": "LOOP #11 PV",
|
|
"archive": true
|
|
}
|
|
```
|
|
|
|
| 필드 | 타입 | 설명 |
|
|
|------|------|------|
|
|
| `tag` | string | 태그명 (Experion ItemName + 파라미터) |
|
|
| `addr` | uint16 | Modbus holding register 주소 (0-based) |
|
|
| `write_addr` | uint16 | 쓰기 주소 (기본값 = addr, MODE 등 별도 쓰기 레지스터가 있는 경우 다름) |
|
|
| `count` | uint16 | 레지스터 수 (float32=2, uint16=1) |
|
|
| `type` | string | 데이터 타입 (`float32` 또는 `uint16`) |
|
|
| `access` | string | 접근 권한 (`R` 또는 `RW`) |
|
|
| `description` | string | 설명 |
|
|
| `archive` | bool | history_table 기록 대상 여부 |
|
|
|
|
### 6.2 Loop 엔트리 (개별 파라미터)
|
|
|
|
```json
|
|
{
|
|
"tag": "FICQ-6101.PV", "addr": 2624, "count": 2, "type": "float32", "access": "R", "archive": true,
|
|
"tag": "FICQ-6101.SP", "addr": 2628, "count": 2, "type": "float32", "access": "RW", "archive": true,
|
|
"tag": "FICQ-6101.OP", "addr": 2630, "count": 2, "type": "float32", "access": "RW", "archive": true,
|
|
"tag": "FICQ-6101.MD", "addr": 2750, "count": 1, "type": "uint16", "access": "R", "archive": true,
|
|
"tag": "FICQ-6101.GAIN", "addr": 2636, "count": 2, "type": "float32", "access": "RW", "archive": false,
|
|
"tag": "FICQ-6101.RESET", "addr": 2640, "count": 2, "type": "float32", "access": "RW", "archive": false,
|
|
...
|
|
}
|
|
```
|
|
|
|
### 6.3 일반 TAG 엔트리
|
|
|
|
```json
|
|
{
|
|
"tag": "LI-9100",
|
|
"addr": 8194,
|
|
"write_addr": 8194,
|
|
"count": 2,
|
|
"type": "float32",
|
|
"access": "R",
|
|
"description": "Signal Tag #2",
|
|
"archive": true
|
|
}
|
|
```
|
|
|
|
### 6.4 Variable 엔트리 (MATH_VAR)
|
|
|
|
```json
|
|
{
|
|
"tag": "P-9114.OP",
|
|
"addr": 6280,
|
|
"write_addr": 6280,
|
|
"count": 2,
|
|
"type": "float32",
|
|
"access": "RW",
|
|
"description": "Variable (MATH_VAR) #5",
|
|
"archive": false
|
|
}
|
|
```
|
|
|
|
### 6.5 FlexibleParameter 엔트리
|
|
|
|
```json
|
|
{
|
|
"tag": "FICQ-6101.QV",
|
|
"addr": 8198,
|
|
"write_addr": 8198,
|
|
"count": 2,
|
|
"type": "float32",
|
|
"access": "R",
|
|
"description": "FlexibleParameter QV",
|
|
"archive": true
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 7. 아카이브/모니터링 제어
|
|
|
|
### 7.1 문제 정의
|
|
|
|
현재 시스템은 다음과 같이 동작:
|
|
|
|
```
|
|
hc900_map_master WHERE is_active = TRUE
|
|
→ realtime_table upsert (1초)
|
|
→ history_table snapshot (60초, digital 제외)
|
|
```
|
|
|
|
**문제점:**
|
|
- `realtime_table`: GAIN1, RESET1, AL1SP1 등 불필요한 태그까지 표시
|
|
- `history_table`: 아카이브할 가치 없는 태그까지 60초마다 기록 (GAIN1, BIAS, RATIO, DEV 등)
|
|
- `is_active` 하나로만 제어 → 실시간 표시와 이력 기록을 분리 불가
|
|
|
|
### 7.2 대상 선별 규칙
|
|
|
|
| 태그 유형 | 예시 | realtime | history | 근거 |
|
|
|-----------|------|:--------:|:-------:|------|
|
|
| **Loop PV** | `FICQ-6101.PV` | ✅ | ✅ | 공정값, 추세 필수 |
|
|
| **Loop SP** | `FICQ-6101.SP` | ✅ | ✅ | 설정값 변경 이력 |
|
|
| **Loop OP** | `FICQ-6101.OP` | ✅ | ✅ | 출력값 추세 |
|
|
| **Loop MODE** | `FICQ-6101.MD` | ✅ | ✅ | Auto/Man 전환 이력 |
|
|
| **Loop QV** | `FICQ-6101.QV` | ✅ | ✅ | Quality Value 추세 |
|
|
| **AnalogPoint PV** | `LI-9100` | ✅ | ✅ | 일반 공정값 |
|
|
| **StatusPoint PV** | `P-9114` | ✅ | ✅ | 상태 변화 (event_history_table) |
|
|
| **Loop 기타 파라미터** | `FICQ-6101.GAIN1`, `.RESET1`, `.RATE1`, `.AL1SP1`, `.DIR`, `.PV_LO`, `.PV_HI`, `.SP_LO`, `.SP_HI`, `.DEV` | ✅ | ❌ | 설정값, 실시간 확인만 |
|
|
| **Variable (MATH_VAR)** | `P-9114.RST`, `FICQ-6101.RST` | ❌ | ❌ | 중간 계산값, 불필요 |
|
|
| **StatusPoint OP** | `P-9114.OP` | ❌ | ❌ | MATH_VAR 기반 |
|
|
| **기타 FlexibleParameter** | `P-9114.STATE`, `FICQ-6101.AL1SP1` | △ | ❌ | 필요시 사용자 활성화 |
|
|
|
|
### 7.3 archive 플래그 선별 로직
|
|
|
|
```python
|
|
def should_archive(kind: str, param_name: str | None) -> bool:
|
|
if kind == 'loop':
|
|
return param_name in ('PV', 'SP', 'OP', 'MD', 'QV')
|
|
if kind == 'tag': # AnalogPoint / StatusPoint TAG source
|
|
return True
|
|
if kind in ('var', 'raw'):
|
|
return False
|
|
if kind == 'flexparam':
|
|
return False
|
|
return False
|
|
```
|
|
|
|
### 7.4 hc900_map_master 컬럼 추가
|
|
|
|
```sql
|
|
ALTER TABLE hc900_map_master
|
|
ADD COLUMN IF NOT EXISTS realtime_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
ADD COLUMN IF NOT EXISTS archive_enabled BOOLEAN NOT NULL DEFAULT FALSE;
|
|
```
|
|
|
|
| 컬럼 | 설명 | 기본값 |
|
|
|------|------|--------|
|
|
| `realtime_enabled` | realtime_table 표시 여부 | TRUE (모든 활성 태그) |
|
|
| `archive_enabled` | history_table 기록 여부 | register-map의 `archive` 필드 |
|
|
|
|
사용자는 PointBuilder UI에서 두 플래그를 개별적으로 override 가능:
|
|
|
|
| 태그명 | 실시간 | 이력 |
|
|
|--------|:-----:|:----:|
|
|
| FICQ-6101.PV | ☑ | ☑ |
|
|
| FICQ-6101.SP | ☑ | ☑ |
|
|
| FICQ-6101.OP | ☑ | ☑ |
|
|
| FICQ-6101.MD | ☑ | ☑ |
|
|
| FICQ-6101.QV | ☑ | ☑ |
|
|
| FICQ-6101.GAIN1 | ☑ | ☐ |
|
|
| LI-9100 | ☑ | ☑ |
|
|
| P-9114 | ☑ | ☑ |
|
|
| P-9114.RST | ☐ | ☐ |
|
|
|
|
### 7.5 마이그레이션
|
|
|
|
```sql
|
|
-- 신규 컬럼 추가
|
|
ALTER TABLE hc900_map_master
|
|
ADD COLUMN IF NOT EXISTS realtime_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
ADD COLUMN IF NOT EXISTS archive_enabled BOOLEAN NOT NULL DEFAULT FALSE;
|
|
|
|
-- build_register_map_from_sinam.py 가 archive=true 인 태그 UPDATE
|
|
-- (스크립트가 hc900_map_master upsert 시 archive_enabled 도 함께 설정)
|
|
```
|
|
|
|
---
|
|
|
|
## 8. 컨트롤러별 독립 맵 전략
|
|
|
|
### 8.1 컨트롤러 구성
|
|
|
|
| 컨트롤러 | IP | 포트 | gRPC Port | 현재 상태 |
|
|
|:--------:|:--:|:----:|:---------:|:---------:|
|
|
| C1 | 192.168.0.250 | 502 | 50051 | 미연결 |
|
|
| C2 | 192.168.0.230 | 502 | 50052 | 미연결 |
|
|
| C3 | 192.168.0.240 | 502 | 50053 | **활성** |
|
|
| C4 | 192.168.0.220 | 502 | 50054 | 미연결 |
|
|
|
|
### 8.2 설정 파일
|
|
|
|
```json
|
|
// config/gateway-config.json
|
|
{
|
|
"controllers": [
|
|
{ "id": "C1", "host": "192.168.0.250", "port": 502,
|
|
"map_path": "docs/register-map-c1.json", "grpc_port": 50051, "enabled": false },
|
|
{ "id": "C2", "host": "192.168.0.230", "port": 502,
|
|
"map_path": "docs/register-map-c2.json", "grpc_port": 50052, "enabled": false },
|
|
{ "id": "C3", "host": "192.168.0.240", "port": 502,
|
|
"map_path": "docs/register-map-c3.json", "grpc_port": 50053, "enabled": true },
|
|
{ "id": "C4", "host": "192.168.0.220", "port": 502,
|
|
"map_path": "docs/register-map-c4.json", "grpc_port": 50054, "enabled": false }
|
|
]
|
|
}
|
|
```
|
|
|
|
### 8.3 컨트롤러 등급별 구분
|
|
|
|
각 컨트롤러는 독립된 C++ 게이트웨이 프로세스로 동작:
|
|
- 각자의 register-map-cN.json 로드
|
|
- 각자의 IP/Port로 Modbus TCP 연결
|
|
- 각자의 gRPC 포트에서 서비스
|
|
- C# ControllerGrpcClientPool이 enabled 컨트롤러별로 클라이언트 생성
|
|
|
|
---
|
|
|
|
## 9. 데이터 흐름 (전면 개정)
|
|
|
|
### 9.1 전체 흐름
|
|
|
|
```
|
|
Sinam_Tag_all.xlsx (단일 진실 공급원)
|
|
│
|
|
▼
|
|
build_register_map_from_sinam.py
|
|
├─ 주소 변환 (TAG/MATH_VAR/LOOP)
|
|
├─ 컨트롤러별 분할
|
|
├─ archive 플래그 설정
|
|
├─ tag_metadata upsert (상태 레이블, 단위, desc, area)
|
|
└─ hc900_map_master upsert (is_active, realtime_enabled, archive_enabled)
|
|
│
|
|
▼ 각 컨트롤러별
|
|
register-map-c1.json ──── C++ Gateway C1 ── gRPC:50051
|
|
register-map-c2.json ──── C++ Gateway C2 ── gRPC:50052
|
|
register-map-c3.json ──── C++ Gateway C3 ── gRPC:50053
|
|
register-map-c4.json ──── C++ Gateway C4 ── gRPC:50054
|
|
│
|
|
▼
|
|
Hc900RealtimeService (C# BackgroundService)
|
|
1. hc900_map_master WHERE is_active=TRUE
|
|
AND realtime_enabled=TRUE AND controller_id=$1
|
|
2. client.ReadTagsAsync(tagNames) ← 1초 간격
|
|
3. BatchUpdateRealtimeTableAsync()
|
|
→ INSERT INTO realtime_table
|
|
(controller_id, tagname, node_id, livevalue, timestamp)
|
|
ON CONFLICT (controller_id, tagname) DO UPDATE ...
|
|
│
|
|
▼
|
|
realtime_table (최신값, tag당 1행, 1초 갱신)
|
|
│
|
|
▼ 60초 간격
|
|
Hc900HistoryService (C# BackgroundService)
|
|
1. hc900_map_master WHERE archive_enabled=TRUE
|
|
2. realtime_table JOIN archive_enabled → tagged points
|
|
3. stamp DateTime.UtcNow
|
|
4. INSERT INTO history_table (TimeScaleDB hypertable)
|
|
│
|
|
▼
|
|
event_history_table (Hc900DigitalEventDetectorService)
|
|
└─ StatusPoint PV 변화 감지 → ALARM/TRIP/RUN/NORMAL
|
|
```
|
|
|
|
### 9.2 C++ 게이트웨이 상세
|
|
|
|
```
|
|
C++ Gateway (hc900_gateway)
|
|
LoadRegisterMap()
|
|
→ JSON 파싱 → registers_[] + tag_index_[]
|
|
→ sorted_indices_[] (addr 정렬)
|
|
|
|
PollLoop()
|
|
→ ReadAllRegisters():
|
|
registers_를 addr 순서로 스캔
|
|
≤120 regs 윈도우에 들어가는 엔트리들 배치
|
|
→ controller_->read_raw(batch_start, read_count)
|
|
→ 각 엔트리 decode_float(FP_B) → cache_[tag_name]
|
|
|
|
gRPC ReadTags()
|
|
→ cache_ 에서 즉시 반환 (Modbus I/O 없음)
|
|
|
|
gRPC WriteTag(tag, value)
|
|
→ transport_mutex_ 획득
|
|
→ controller_->write_raw(entry.write_addr, ...) ← FC16
|
|
→ cache_ 업데이트
|
|
|
|
gRPC HealthCheck()
|
|
→ 연결 상태, poll_count, last_poll_duration
|
|
```
|
|
|
|
---
|
|
|
|
## 10. build_register_map_from_sinam.py 처리 규칙
|
|
|
|
### 10.1 실행 명령어
|
|
|
|
```bash
|
|
python3 scripts/build_register_map_from_sinam.py \
|
|
--sinam docs/Sinam_Tag_all.xlsx \
|
|
--controller C3 \
|
|
-o docs/register-map-c3.json \
|
|
[--db-conn "Host=...;Database=hc900;..."]
|
|
```
|
|
|
|
### 10.2 처리 순서
|
|
|
|
1. **Sinam_Tag_all.xlsx 로드** (openpyxl, read_only=True, data_only=True)
|
|
2. **Section 1 처리** (ItemName 행):
|
|
- 각 행의 모든 셀에서 정규식으로 C{N} LOOP/TAG/MATH_VAR/LOOPX/RAW 검색
|
|
- `scan_point()` 분류: loop / tag / var / raw
|
|
- Loop: `build_loop_entries()` → 모든 LOOP_LAYOUT 파라미터를 개별 엔트리로 전개
|
|
- Tag: `build_point_entry()` → 단일 엔트리
|
|
- Var: `build_point_entry()` → 단일 엔트리
|
|
3. **Section 2+ 처리** (FlexibleParameters 행):
|
|
- `resolve_one()` 으로 개별 주소 해석
|
|
- `ParentItemName.ParamName` 태그명 생성
|
|
4. **`archive` 플래그 설정** (`should_archive()`)
|
|
5. **컨트롤러별 필터링** (--controller 인자)
|
|
6. **주소 순서 정렬** 후 JSON 출력
|
|
7. **선택적 DB 적재**:
|
|
- `tag_metadata` upsert (상태 레이블, 단위, desc, area)
|
|
- `hc900_map_master` upsert (is_active, realtime_enabled, archive_enabled)
|
|
|
|
### 10.3 Loop 엔트리 전개 상세
|
|
|
|
`build_loop_entries(item_name, loop_no, mnemonics)`:
|
|
|
|
```
|
|
입력: item_name="FICQ-6101", loop_no=11, mnemonics={PV, WSP, OPWORK, LOOPSTAT}
|
|
base = loop_base(11) = 0x0A40 = 2624
|
|
|
|
LOOP_LAYOUT 각 offset을 순회:
|
|
off=0x00 → suffix="PV", attr=EXPERION_ATTR.get("PV")="PV"
|
|
→ tag="FICQ-6101.PV", addr=2624, archive=true
|
|
off=0x04 → suffix="WSP", attr=EXPERION_ATTR.get("WSP")="SP"
|
|
→ tag="FICQ-6101.SP", addr=2628, archive=true
|
|
off=0x06 → suffix="Output", attr=EXPERION_ATTR.get("OPWORK")="OP"
|
|
→ tag="FICQ-6101.OP", addr=2630, archive=true
|
|
off=0xBE → has_mode=True → 별도 .MD 엔트리
|
|
→ tag="FICQ-6101.MD", addr=2750 (=2624+0xBE),
|
|
write_addr=2746 (=2624+0xBA), archive=true
|
|
|
|
나머지 LOOP_LAYOUT 파라미터는 HC900명 유지:
|
|
off=0x0C → tag="FICQ-6101.Gain1_PropBand1", addr=2636, archive=false
|
|
off=0x10 → tag="FICQ-6101.Reset1", addr=2640, archive=false
|
|
...
|
|
```
|
|
|
|
### 10.4 MODE write_addr 처리
|
|
|
|
```python
|
|
# Loop Status 0xBE → read source
|
|
# Auto/Man State 0xBA → write target
|
|
if has_mode:
|
|
entries.append({
|
|
'tag': f'{item_name}.MD',
|
|
'addr': base + 0xBE, # 읽기 = Loop Status
|
|
'write_addr': base + 0xBA, # 쓰기 = Auto/Man State ⚠️
|
|
'count': 1,
|
|
'type': 'uint16',
|
|
'access': 'R', # 읽기전용 (게이트웨이가 MD 쓰기를 MODEIN으로 라우팅)
|
|
'description': f'LOOP #{n} Mode status',
|
|
'archive': True,
|
|
})
|
|
```
|
|
|
|
⚠️ 초기 버전에서 `write_addr = base + 0xBE`로 잘못 설정된 버그 수정 완료.
|
|
올바른 값: 읽기=0xBE(LoopStatus), 쓰기=0xBA(Auto/Man State).
|
|
|
|
### 10.5 archive 플래그 설정
|
|
|
|
```python
|
|
def should_archive(kind: str, is_loop: bool, param_name: str | None,
|
|
access: str, is_signal_tag: bool) -> bool:
|
|
"""history_table 기록 대상 선별"""
|
|
if is_loop and param_name in ('PV', 'SP', 'OP', 'MD', 'QV'):
|
|
return True
|
|
if not is_loop and access == 'R' and is_signal_tag:
|
|
return True # AnalogPoint / StatusPoint TAG source (PV)
|
|
return False
|
|
```
|
|
|
|
---
|
|
|
|
## 11. C# 서비스 수정 사항
|
|
|
|
### 11.1 Hc900MapEntry 엔티티
|
|
|
|
```csharp
|
|
// src/Core/Domain/Entities/Hc900Entities.cs
|
|
[Table("hc900_map_master")]
|
|
public class Hc900MapEntry
|
|
{
|
|
[Key] public int Id { get; set; }
|
|
|
|
[Column("tagname")] public string TagName { get; set; } = "";
|
|
[Column("hc900_tag")] public string Hc900Tag { get; set; } = "";
|
|
[Column("modbus_addr")] public int ModbusAddr { get; set; }
|
|
[Column("data_type")] public string DataType { get; set; } = "float32";
|
|
[Column("access")] public string Access { get; set; } = "R";
|
|
[Column("is_active")] public bool IsActive { get; set; } = true;
|
|
[Column("controller_id")] public string ControllerId { get; set; } = "HC1";
|
|
[Column("realtime_enabled")] public bool RealtimeEnabled { get; set; } = true; // 신규
|
|
[Column("archive_enabled")] public bool ArchiveEnabled { get; set; } = false; // 신규
|
|
}
|
|
```
|
|
|
|
### 11.2 Hc900RealtimeService.LoadMappingAsync
|
|
|
|
```csharp
|
|
// 변경 전
|
|
"SELECT tagname, hc900_tag FROM hc900_map_master WHERE is_active = TRUE AND controller_id = $1"
|
|
|
|
// 변경 후
|
|
"SELECT tagname, hc900_tag FROM hc900_map_master WHERE is_active = TRUE AND realtime_enabled = TRUE AND controller_id = $1"
|
|
```
|
|
|
|
### 11.3 Hc900HistoryService.SnapshotToHistoryAsync
|
|
|
|
```csharp
|
|
// 변경 전
|
|
var digitalTagNames = await GetDigitalTagNamesCachedAsync();
|
|
var points = await _ctx.RealtimePoints
|
|
.Where(p => !digitalTagNames.Contains(p.TagName))
|
|
.ToListAsync();
|
|
|
|
// 변경 후
|
|
var archiveTags = await _ctx.Hc900MapEntries
|
|
.Where(m => m.ArchiveEnabled)
|
|
.Select(m => m.TagName)
|
|
.ToListAsync();
|
|
|
|
var points = await _ctx.RealtimePoints
|
|
.Where(p => archiveTags.Contains(p.TagName))
|
|
.ToListAsync();
|
|
```
|
|
|
|
### 11.4 DDL (멱등)
|
|
|
|
```csharp
|
|
await _ctx.Database.ExecuteSqlRawAsync("""
|
|
ALTER TABLE hc900_map_master
|
|
ADD COLUMN IF NOT EXISTS realtime_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
ADD COLUMN IF NOT EXISTS archive_enabled BOOLEAN NOT NULL DEFAULT FALSE
|
|
""");
|
|
```
|
|
|
|
---
|
|
|
|
## 12. tag_metadata 적재
|
|
|
|
`build_register_map_from_sinam.py` 실행 시 `--db-conn` 옵션으로 tag_metadata도 함께 upsert:
|
|
|
|
| attribute | 출처 (Sinam_Tag_all.xlsx) |
|
|
|-----------|--------------------------|
|
|
| `desc` | `ItemDescription` 또는 `DownloadedName` |
|
|
| `area` | `AreaCode` (예: `P6`, `P9`) |
|
|
| `state0~7` | `DescriptorState0~7` (StatusPoint) / `DescriptorState0~7UDSP` (FlexibleParameters) |
|
|
| `units` | `Units` (AnalogPoint) / `UnitsUDSP` (FlexibleParameters) |
|
|
| `eulo` / `euhi` | `RangeLow` / `RangeHigh` (AnalogPoint) / `RangeLowUDSP` / `RangeHighUDSP` (FlexibleParameters) |
|
|
|
|
```sql
|
|
INSERT INTO tag_metadata (base_tag, attribute, value, controller_id)
|
|
VALUES (%s, %s, %s, %s)
|
|
ON CONFLICT (base_tag, attribute, controller_id)
|
|
DO UPDATE SET value = EXCLUDED.value, loaded_at = NOW()
|
|
```
|
|
|
|
---
|
|
|
|
## 13. 알려진 문제
|
|
|
|
### 13.1 MODE write_addr 버그 (수정 완료)
|
|
- `build_register_map_from_sinam.py` 초기 버전: MODE 엔트리의 `write_addr = base + 0xBE` (LoopStatus, 읽기전용)
|
|
- 올바름: `write_addr = base + 0xBA` (Auto/Man State, R/W)
|
|
- 영향: 게이트웨이를 통한 MODE 쓰기(MAN→AUTO 전환)가 HC900에 반영되지 않음
|
|
- 해결: 개정 코드에서 `MD_WRITE_OFFSET = 0xBA` 사용
|
|
|
|
### 13.2 LOOPX (Custom Map) 주소 검증
|
|
- `C4 LOOPX 25 AL1SP1` 등의 Custom Map 영역
|
|
- HC Designer Custom Map 보고서 필요 (현재 SummaryFunctionBlockReport.csv는 Fixed Map 기준)
|
|
- 주소 체계 검증: LOOPX 25~32는 Loop 25~32와 동일한 `0x7840 + (N-25)*0x0100` 사용한다고 가정
|
|
|
|
### 13.3 C4 PID Loop 데이터 부재
|
|
- `FICQ-9101`, `FICQ-9214` 등 C4 루프 태그는 Sinam_Tag_all.xlsx에 SourceAddress가 있음
|
|
- HC Designer CSV는 C4(Custom Map) PID Loop 데이터 없음 (빈 맵)
|
|
- PointBuilder로 수동 등록 또는 HC Designer Custom Map Export 필요
|
|
|
|
### 13.4 Config 게이트웨이 경로
|
|
- `gateway-config.json`이 구버전 `register-map-c3.json`(2189개 엔트리, HC900명) 참조 중
|
|
- 신규 `register-map-c3.json`(Experion명) 생성 후 경로 업데이트 필요
|
|
|
|
### 13.5 hc900_map_master.tagname = hc900_tag 중복
|
|
- 현재 `tagname`(소문자 OPC UA 명)과 `hc900_tag`(대문자 Experion 명)가 동일해짐
|
|
- 향후 `tagname` 컬럼 제거하고 `hc900_tag`로 통일 가능
|
|
- 마이그레이션 동안은 두 컬럼 모두 동일한 값(Experion 대문자명) 유지
|
|
|
|
---
|
|
|
|
## 14. 폐기 항목
|
|
|
|
| 항목 | 사유 | 대체 |
|
|
|------|------|------|
|
|
| `load_state_labels.py` | Sinam_Tag_all.xlsx 통합 | `build_register_map_from_sinam.py`에 통합 |
|
|
| HC Designer CSVs (`SignalTags.csv` 등) | 주소 검증용 보조 자료 | Sinam_Tag_all.xlsx가 주 소스 |
|
|
| OPC UA `node_id` | HC900 직결 방식에서 불필요 | 빈 문자열로 저장 |
|
|
| 소문자 태그명 (`ficq-6101.pv`) | OPC UA 강제 규칙 폐기 | 대문자 (`FICQ-6101.PV`) |
|
|
| `build_register_map.py` (기존) | CSV 의존, Experion명 미지원 | `build_register_map_from_sinam.py` |
|
|
| `build_register_map_from_csv.py` | CSV Full Map 단순 변환 | Sinam 기반 스크립트로 대체 |
|
|
| 개별 루프 파라미터 → 루프 블록 통째 | 게이트웨이 MAX_BATCH=120 제약 | 개별 파라미터 유지 (게이트웨이가 동적 배치) |
|
|
|
|
---
|
|
|
|
## 15. 마이그레이션 순서
|
|
|
|
### Phase A: 스크립트 완성
|
|
|
|
| 단계 | 작업 | 담당 |
|
|
|:----:|------|------|
|
|
| A1 | `build_register_map_from_sinam.py` MODE write_addr 버그 수정 (0xBE→0xBA) | Python |
|
|
| A2 | `build_register_map_from_sinam.py`에 `archive` 필드 추가 + 선별 로직 구현 | Python |
|
|
| A3 | `build_register_map_from_sinam.py`에 tag_metadata upsert 로직 추가 | Python |
|
|
| A4 | `build_register_map_from_sinam.py`에 hc900_map_master upsert 로직 추가 | Python |
|
|
| A5 | C1~C4 전 컨트롤러 register-map 생성 테스트 | Python |
|
|
|
|
### Phase B: DB 마이그레이션
|
|
|
|
| 단계 | 작업 | 담당 |
|
|
|:----:|------|------|
|
|
| B1 | `hc900_map_master`에 `realtime_enabled`, `archive_enabled` DDL 추가 | C# |
|
|
| B2 | Phase A에서 생성한 register-map으로 hc900_map_master 초기 적재 | Python |
|
|
|
|
### Phase C: C# 서비스 수정
|
|
|
|
| 단계 | 작업 | 담당 |
|
|
|:----:|------|------|
|
|
| C1 | `Hc900MapEntry`에 `RealtimeEnabled`, `ArchiveEnabled` 속성 추가 | C# |
|
|
| C2 | `Hc900RealtimeService.LoadMappingAsync`에 `AND realtime_enabled = TRUE` 추가 | C# |
|
|
| C3 | `SnapshotToHistoryAsync`에 `archive_enabled` 필터 적용 | C# |
|
|
| C4 | PointBuilder UI에 실시간/이력 토글 컬럼 추가 | C# |
|
|
|
|
### Phase D: 배포
|
|
|
|
| 단계 | 작업 | 담당 |
|
|
|:----:|------|------|
|
|
| D1 | C++ 게이트웨이 신규 register-map-c3.json으로 교체 | Config |
|
|
| D2 | `gateway-config.json` 경로 업데이트 | Config |
|
|
| D3 | C# 서비스 재시작 | Ops |
|
|
| D4 | 기존 `archive_enabled = FALSE` → register-map 기준 UPDATE | SQL |
|
|
|
|
---
|
|
|
|
## 관련 파일
|
|
|
|
| 파일 | 설명 |
|
|
|------|------|
|
|
| `docs/Sinam_Tag_all.xlsx` | **단일 진실 공급원** — 모든 태그 정의 + 메타데이터 |
|
|
| `docs/51-52-25-111-HC900-Process-Controller-Communications-manual.pdf` | HC900 통신 매뉴얼 (Table 6-1, 6-3) |
|
|
| `docs/register-map-cN.json` | 컨트롤러별 레지스터 맵 (build_register_map_from_sinam.py 생성) |
|
|
| `config/gateway-config.json` | 게이트웨이 프로세스 설정 (IP, port, 맵 경로) |
|
|
| `scripts/build_register_map_from_sinam.py` | **단일 빌드 스크립트** — 맵 생성 + 메타데이터 + archive 플래그 |
|
|
| `src/Infrastructure/Hc900/Hc900RealtimeService.cs` | 실시간 폴링 서비스 (gRPC → realtime_table) |
|
|
| `src/Infrastructure/Hc900/Hc900HistoryService.cs` | 이력 스냅샷 서비스 (realtime_table → history_table) |
|
|
| `src/Infrastructure/Database/Hc900DbContext.cs` | DB 컨텍스트 (테이블 정의, 마이그레이션 + SnapshotToHistoryAsync) |
|
|
| `src/Core/Domain/Entities/Hc900Entities.cs` | `Hc900MapEntry` 엔티티 (hc900_map_master 매핑) |
|
|
| `industrial-comm/cpp/src/gateway.cpp` | C++ 게이트웨이 (Modbus 폴링 + gRPC + 동적 배치 그룹핑) |
|
|
| `industrial-comm/cpp/src/modbus_tcp.cpp` | Modbus TCP 전송 계층 (FC03, FC16) |
|
|
| `industrial-comm/cpp/src/vendor_formats.hpp` | FP_B float format (`bigEndian, highFirst, normal`) |
|