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>
346 lines
12 KiB
Markdown
346 lines
12 KiB
Markdown
# 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 다중 레지스터).
|