Files
HC900-Crawler/docs/experion-indexed-address-mapping.md
windpacer 16fc7a2598 Initial commit: HC900 Crawler
Honeywell HC900을 Modbus TCP로 직접 폴링 → gRPC → C# 크롤러 → PostgreSQL.
기존 Experion OPC UA 데이터 경로를 HC900 직접 통신으로 대체.

- industrial-comm/cpp: C++ Modbus 게이트웨이 (gRPC 서버)
- src: C# .NET 8 ASP.NET Core 크롤러 + 웹 UI (3-Layer)
- mcp-server: Python FastMCP (RAG/NL2SQL/P&ID)
- 다중 컨트롤러(N-Controller) 지원

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:28:14 +09:00

346 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Experion Indexed Address → HC900 Fixed Modbus Memory Map 변환 공식
**대상**: HC900-C70 (C4 컨트롤러), Experion HS R530 OPC 서버
**출처**: `C4-All-Modbus-Map.csv`, `Sinam_Tag_all.xlsx` (SourceAddressPV/OP/MD 컬럼), HC900 Communications Manual Rev.13
---
## 1. 배경
Experion OPC 서버는 HC900 Modbus 레지스터를 직접 주소 대신 **Indexed Address** 형식으로 참조한다.
`Sinam_Tag_all.xlsx``SourceAddressPV` / `SourceAddressOP` / `SourceAddressMD` 컬럼에 이 형식이 기록된다.
```
예시:
C4 LOOP 1 LOOPSTAT → C4 컨트롤러, PID Loop #1, Loop Status Register
C4 TAG 32 VALUE → C4 컨트롤러, Signal Tag #32, 값
C4 MATH_VAR 5 VALUE → C4 컨트롤러, Variable #5, 값
C4 LOOPX 27 LOOPSTAT → C4 컨트롤러, 확장 Loop #27 (25-32), Loop Status Register
```
---
## 2. Indexed Address 형식
```
{CTRL} {PARTITION} {N} {PARAM}
CTRL : 컨트롤러 식별자 (C1, C2, C3, C4)
PARTITION : LOOP | LOOPX | TAG | MATH_VAR
N : 1-based 인덱스 번호
PARAM : 파라미터명 (하단 §5 참조)
```
---
## 3. HC900 Fixed Map 파티션별 Modbus 주소 공식
### 3-1. PID Loop (1-24번) — `LOOP`
```
base_addr(N) = 0x0040 + (N - 1) × 0x0100 (N = 1 ~ 24)
Modbus_addr = base_addr(N) + param_offset
```
| Loop# | Base Addr | HC900 태그명 (C4) |
|------:|-----------|------------------|
| 1 | 0x0040 | FIC-9101 |
| 2 | 0x0140 | FICQ-9114 |
| 3 | 0x0240 | FIC-9113 |
| 4 | 0x0340 | FIC-9118 |
| 5 | 0x0440 | FIC-9116 |
| 6 | 0x0540 | TIC-9111A |
| 7 | 0x0640 | PIC-9111A |
| 8 | 0x0740 | FIC-9201 |
| 9 | 0x0840 | FIC-9213 |
| 10 | 0x0940 | FIC-9214 |
| 11 | 0x0A40 | FIC-9218 |
| 12 | 0x0B40 | FIC-9216 |
| 13 | 0x0C40 | TIC-9211 |
| 14 | 0x0D40 | PIC-9211B |
| 15 | 0x0E40 | LIC-9113 |
| 16 | 0x0F40 | LIC-9213 |
| 17 | 0x1040 | FICQ-10101 |
| 18 | 0x1140 | FICQ-10113 |
| 19 | 0x1240 | FICQ-10114A |
| 20 | 0x1340 | FIC-10118 |
| 21 | 0x1440 | LICA-10113 |
| 22 | 0x1540 | FICQ-10116 |
| 23 | 0x1640 | TIC-10111A |
| 24 | 0x1740 | PIC-10111A |
### 3-2. 확장 PID Loop (25-32번) — `LOOPX`
```
base_addr(N) = 0x7840 + (N - 25) × 0x0100 (N = 25 ~ 32)
Modbus_addr = base_addr(N) + param_offset
```
| Loop# | Base Addr | HC900 태그명 (C4) |
|------:|-----------|------------------|
| 25 | 0x7840 | FICQ-10201 |
| 26 | 0x7940 | FICQ-10213 |
| 27 | 0x7A40 | FICQ-10214 |
| 28 | 0x7B40 | FICQ-10218 |
| 29 | 0x7C40 | LIC-10213 |
| 30 | 0x7D40 | FIC-10216 |
| 31 | 0x7E40 | TIC-10211 |
| 32 | 0x7F40 | PIC-10211A |
### 3-3. Signal Tag (1-1000번) — `TAG`
```
Modbus_addr(N) = 0x2000 + (N - 1) × 2 (N = 1 ~ 1000)
데이터 타입: float 32 (2 레지스터), Read-only
```
> Signal Tag 번호와 HC900 태그명의 매핑은 HC Designer "Tag Information" 리포트 또는
> `C4-All-Modbus-Map.csv` > `Signal Tags 1-1000` 파티션 참조.
### 3-4. Variable / Math Variable (1-600번) — `MATH_VAR`
```
Modbus_addr(N) = 0x18C0 + (N - 1) × 2 (N = 1 ~ 600)
데이터 타입: float 32 (2 레지스터), Read/Write
```
---
## 4. 주소 영역 요약
| Partition | Experion 키워드 | 시작 주소 | 끝 주소 | 공식 | 개수 |
|-----------|----------------|----------|---------|------|------|
| System Parameters | — | 0x0000 | 0x003F | — | — |
| PID Loop 1-24 | `LOOP` | 0x0040 | 0x17FF | 0x0040+(N-1)×0x100 | 24 loops |
| PID Loop 25-32 | `LOOPX` | 0x7840 | 0x7FFF | 0x7840+(N-25)×0x100 | 8 loops |
| Variable 1-600 | `MATH_VAR` | 0x18C0 | 0x1D6F | 0x18C0+(N-1)×2 | 600 tags |
| Signal Tag 1-1000 | `TAG` | 0x2000 | 0x27CF | 0x2000+(N-1)×2 | 1000 tags |
| Signal Tag 1-4000 | — | 0x3B60 | 0x5A9F | 0x3B60+(N-1)×2 | 4000 tags |
---
## 5. Loop 파라미터 오프셋 테이블
`C4-All-Modbus-Map.csv` Loop 1 (FIC-9101, base=0x0040) 기준 실측값.
모든 루프에 동일하게 적용된다.
### 5-1. Float 파라미터 (float 32 = 2 레지스터)
| Experion 파라미터명 | HC900 태그 접미사 | Offset (Hex) | Offset (Dec) | Access | 설명 |
|-------------------|----------------|:------------:|:------------:|:------:|------|
| `PV` | `.PV` | +0x0000 | +0 | R | 프로세스 변수 |
| `RSP` | `.RSP_SP2` | +0x0002 | +2 | R/W | Remote SP (SP2) |
| `WSP` | `.WSP` | +0x0004 | +4 | R/W | Working Set Point |
| `OP` | `.Output` | +0x0006 | +6 | R/W | Output |
| — | `.PV__B_` | +0x0008 | +8 | R | PV (B bank) |
| — | `.Gain_1_Prop_Band_1` | +0x000C | +12 | R/W | Gain 1 / Prop Band 1 |
| — | `.Direction` | +0x000E | +14 | R | Direction (0=Direct, 1=Reverse) |
| `RESET1` | `.Reset_1` | +0x0010 | +16 | R/W | Reset 1 (Integral) |
| `RATE1` | `.Rate_1` | +0x0012 | +18 | R/W | Rate 1 (Derivative) |
| — | `.Scan_Cycle_Time` | +0x0014 | +20 | R | Scan Cycle Time |
| — | `.PV_low_range` | +0x0016 | +22 | R | PV Low Range |
| — | `.PV_high_range` | +0x0018 | +24 | R | PV High Range |
| — | `.Alarm_1_SP1` | +0x001A | +26 | R/W | Alarm 1 SP1 |
| — | `.Alarm_1_SP2` | +0x001C | +28 | R/W | Alarm 1 SP2 |
| — | `.Gain2_Prop_Band_2` | +0x0020 | +32 | R/W | Gain 2 / Prop Band 2 |
| — | `.Reset_2` | +0x0024 | +36 | R/W | Reset 2 |
| — | `.Rate_2` | +0x0026 | +38 | R/W | Rate 2 |
| `LSP1` | `.LSP1` | +0x002A | +42 | R/W | Local SP 1 |
| `LSP2` | `.LSP2` | +0x002C | +44 | R/W | Local SP 2 |
| — | `.Alarm_2_SP1` | +0x002E | +46 | R/W | Alarm 2 SP1 |
| — | `.Alarm_2_SP2` | +0x0030 | +48 | R/W | Alarm 2 SP2 |
| — | `.SP_low_limit` | +0x0034 | +52 | R/W | SP Low Limit |
| — | `.SP_high_limit` | +0x0036 | +54 | R/W | SP High Limit |
| — | `.Output_Low_Limit` | +0x003A | +58 | R/W | Output Low Limit |
| — | `.Output_High_Limit` | +0x003C | +60 | R/W | Output High Limit |
| — | `.Ratio` | +0x0046 | +70 | R/W | Ratio |
| — | `.Bias` | +0x0048 | +72 | R/W | Bias |
| — | `.Deviation` | +0x004A | +74 | R | Deviation (SP-PV) |
| — | `.Manual_Reset` | +0x004E | +78 | R/W | Manual Reset |
| — | `.Feed_forward_Gain` | +0x0050 | +80 | R/W | Feedforward Gain |
### 5-2. Integer 파라미터 (unsigned 16 = 1 레지스터)
| Experion 파라미터명 | HC900 태그 접미사 | Offset (Hex) | Offset (Dec) | Access | 설명 |
|-------------------|----------------|:------------:|:------------:|:------:|------|
| — | `.Enable_Disable_Fuzzy` | +0x00B7 | +183 | R/W | Fuzzy Enable (0/1) |
| — | `.Demand_Tune_Req` | +0x00B8 | +184 | R/W | Autotune Request |
| `AMSTAT` | `.Auto_Man_State` | +0x00BA | +186 | R/W | 0=Manual, 1=Auto |
| — | `.LSP_Select_State` | +0x00BB | +187 | R/W | SP 선택 (0=SP1, 1=SP2) |
| — | `.Rem_Loc_SP_State` | +0x00BC | +188 | R/W | Remote/Local SP |
| — | `.Tune_Set_State` | +0x00BD | +189 | R/W | Tune Set |
| `LOOPSTAT` | `.Loop_Status_Register` | +0x00BE | +190 | R | Loop 상태 (bit-packed) |
> **Loop 블록 크기**: 루프당 0x00C0 (192) 레지스터 할당 (0x0040~0x00FF)
> float 32 = 2 레지스터 사용 (even address), unsigned 16 = 1 레지스터
---
## 6. 주소 계산 예시
### 예시 1: `FICQ-9101` (AnalogPoint) — `C4 LOOP 1 LOOPSTAT`
```
LOOP 번호: 1
base_addr = 0x0040 + (1-1) × 0x0100 = 0x0040
LOOPSTAT 오프셋 = +0x00BE
Modbus_addr = 0x0040 + 0x00BE = 0x00FE (= 255 dec)
HC900 태그: FIC-9101.Loop_Status_Register @ 0x00FE
```
### 예시 2: `FICQ-9101` — `C4 LOOP 1 AMSTAT` (Auto/Manual State)
```
base_addr = 0x0040
AMSTAT 오프셋 = +0x00BA
Modbus_addr = 0x0040 + 0x00BA = 0x00FA (= 250 dec)
HC900 태그: FIC-9101.Auto_Man_State @ 0x00FA
```
### 예시 3: `FICQ-10214` (AnalogPoint) — `C4 LOOPX 27 LOOPSTAT`
```
LOOPX 번호: 27 (25번부터 시작)
base_addr = 0x7840 + (27-25) × 0x0100 = 0x7840 + 0x0200 = 0x7A40
LOOPSTAT 오프셋 = +0x00BE
Modbus_addr = 0x7A40 + 0x00BE = 0x7AFE
HC900 태그: FICQ-10214.Loop_Status_Register @ 0x7AFE
```
### 예시 4: `P-9114` (StatusPoint) — `C4 TAG 32 VALUE`
```
TAG 번호: 32
Modbus_addr = 0x2000 + (32-1) × 2 = 0x2000 + 62 = 0x203E (= 8254 dec)
HC900 태그: P_9114 @ 0x203E (C4-All-Modbus-Map.csv Signal Tag #32)
```
### 예시 5: `XV-9101` (StatusPoint) — `C4 MATH_VAR 1 VALUE` (OP source)
```
MATH_VAR 번호: 1
Modbus_addr = 0x18C0 + (1-1) × 2 = 0x18C0
HC900 태그: XV9101AUTO @ 0x18C0 (C4-All-Modbus-Map.csv Variable #1)
```
---
## 7. 검증 결과 요약 (C4 컨트롤러)
`Sinam_Tag_all.xlsx` × `C4-All-Modbus-Map.csv` 교차 검증 결과:
| 패턴 | 매핑 건수 | 검증 상태 |
|------|:--------:|:--------:|
| `C4 LOOP N *` (1-24) | 24 | ✓ 전수 일치 |
| `C4 LOOPX N *` (25-32) | 8 | ✓ 전수 일치 |
| `C4 TAG N VALUE` | ~120 | ✓ 전수 일치 |
| `C4 MATH_VAR N VALUE` | ~55 | ✓ 전수 일치 |
| **합계** | **207** | **✓** |
---
## 8. Python 공식 구현
```python
def experion_indexed_to_modbus(indexed_addr: str, loop_tag_map: dict) -> dict | None:
"""
Experion Indexed Address → HC900 Modbus 주소 변환.
loop_tag_map: {loop_num: hc900_base_tag}
예: {1: 'FIC-9101', 2: 'FICQ-9114', ...}
반환: {'hc900_tag': str, 'modbus_addr': int, 'dtype': str}
"""
import re
# 파라미터 → (offset, dtype)
PARAM_OFFSET = {
'PV': (0x0000, 'float32'),
'RSP': (0x0002, 'float32'),
'WSP': (0x0004, 'float32'),
'OP': (0x0006, 'float32'),
'RESET1': (0x0010, 'float32'),
'RATE1': (0x0012, 'float32'),
'LSP1': (0x002A, 'float32'),
'LSP2': (0x002C, 'float32'),
'AMSTAT': (0x00BA, 'uint16'),
'LOOPSTAT': (0x00BE, 'uint16'),
'VALUE': (0x0000, 'float32'),
}
# LOOP / LOOPX
m = re.match(r'\w+ (LOOP|LOOPX) (\d+) (\w+)', indexed_addr)
if m:
loop_type, n, param = m.group(1), int(m.group(2)), m.group(3)
base = (0x0040 + (n-1)*0x0100) if loop_type == 'LOOP' \
else (0x7840 + (n-25)*0x0100)
offset, dtype = PARAM_OFFSET.get(param, (None, None))
if offset is None:
return None
hc900_base = loop_tag_map.get(n, f'LOOP{n}')
suffix = {
'PV':'PV','RSP':'RSP_SP2','WSP':'WSP','OP':'Output',
'RESET1':'Reset_1','RATE1':'Rate_1','LSP1':'LSP1','LSP2':'LSP2',
'AMSTAT':'Auto_Man_State','LOOPSTAT':'Loop_Status_Register',
}.get(param, param)
return {'hc900_tag': f'{hc900_base}.{suffix}',
'modbus_addr': base + offset, 'dtype': dtype}
# TAG (Signal Tag)
m = re.match(r'\w+ TAG (\d+) VALUE', indexed_addr)
if m:
n = int(m.group(1))
return {'hc900_tag': f'SIG_TAG_{n}', # 실제 태그명은 CSV 조회 필요
'modbus_addr': 0x2000 + (n-1)*2, 'dtype': 'float32'}
# MATH_VAR (Variable)
m = re.match(r'\w+ MATH_VAR (\d+) VALUE', indexed_addr)
if m:
n = int(m.group(1))
return {'hc900_tag': f'VAR_{n}', # 실제 태그명은 CSV 조회 필요
'modbus_addr': 0x18C0 + (n-1)*2, 'dtype': 'float32'}
return None
# 사용 예
loop_tag_map = {
1: 'FIC-9101', 2: 'FICQ-9114', 3: 'FIC-9113',
# ... (C4-All-Modbus-Map.csv에서 생성)
}
result = experion_indexed_to_modbus('C4 LOOP 1 LOOPSTAT', loop_tag_map)
# → {'hc900_tag': 'FIC-9101.Loop_Status_Register', 'modbus_addr': 0x00FE, 'dtype': 'uint16'}
result = experion_indexed_to_modbus('C4 TAG 32 VALUE', loop_tag_map)
# → {'hc900_tag': 'SIG_TAG_32', 'modbus_addr': 0x203E, 'dtype': 'float32'}
```
---
## 9. 주의사항
1. **이 공식은 Fixed Map 기준**: Custom Map을 사용하는 컨트롤러에서는 주소가 다를 수 있다.
`C4-All-Modbus-Map.csv` ("Modbus All Partitions Report")가 실제 매핑의 최종 정보원.
2. **컨트롤러별 루프 배치가 다름**: C1~C4 각 컨트롤러마다 Loop #1에 배치된 PID 블록이 다르다.
이 문서의 "Loop 번호 → HC900 태그" 테이블은 **C4 컨트롤러 전용**.
3. **Signal Tag 실제 태그명**: `TAG N VALUE` → Modbus 주소는 공식으로 계산하지만,
HC900 태그명 (예: `P_9114`)은 반드시 `C4-All-Modbus-Map.csv`의 Signal Tags 파티션을 조회해야 한다.
4. **Float 바이트 오더**: HC900 기본값은 **FP B (Big Endian)**, 4바이트 순서 = High word first.
Modbus 레지스터 2개 읽은 뒤 `struct.pack('>HH', r[0], r[1])`로 디코딩.
5. **MATH_VAR = Variables**: Experion 용어 "MATH_VAR"은 HC900 "Variable" (0x18C0~)을 가리킨다.
R/W 가능 (Write: FC06 단일 또는 FC16 다중 레지스터).