Files
HC900-Crawler/docs/plan.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

11 KiB
Raw Permalink Blame History

HC900 Gateway — 아키텍처 및 구현 계획

1. 개요

ExperionCrawler의 OPC UA → Experion HS R530 경로를 Modbus TCP → HC900 경로로 대체.

Before:
HC900 ──Modbus TCP──▶ Experion R530 ──OPC UA──▶ ExperionCrawler ──▶ PostgreSQL

After:
HC900 ──Modbus TCP──▶ C++ Gateway ──gRPC──▶ HC900Crawler ──▶ PostgreSQL

2. C++ 게이트웨이 (Modbus TCP → gRPC)

설계 원칙

  • 단순, 고효율, 경량 (예상 RSS 5~8 MB)
  • Full poll 1초 주기 — 모든 레지스터 읽어서 캐시
  • Scattered read는 안 함 — 어차피 Modbus는 batch가 빠름

아키텍처

┌─────────────────────────────────────────────┐
│              C++ Gateway                     │
│  ┌──────────┐  ┌──────────┐  ┌────────────┐ │
│  │ Poller    │─▶│ Cache    │◀─│ gRPC Server│ │
│  │ (1s 주기) │  │ (47KB)   │  │            │ │
│  │ 32+3+13   │  │          │  │ ReadTags   │ │
│  │ batch     │  │          │  │ WriteTag   │ │
│  │ reads     │  │          │  │ ListTags   │ │
│  └────┬──────┘  └──────────┘  └─────┬──────┘ │
│       │                             │        │
│       ▼                             ▼        │
│  ┌─────────────────────────────────────────┐ │
│  │     industrial-comm (libcomm_core)      │ │
│  │     ModbusTCP → HC900 @ 192.168.0.240  │ │
│  └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────┘

폴링 전략

  • read_holding_registers(addr, count=120) batch
  • Loop 1~24: 32 reads (120 regs each)
  • Loop 25~32: 32 reads (120 regs each)
  • Variable area: 3 reads (0x18C0~, 336 regs)
  • Signal area: 13 reads (0x2000~, 1,508 regs)
  • Total: 48 batch reads → ~117 ms full poll
  • 1초 간격 poll, 캐시 갱신

gRPC Service (proto/modbus_gateway.proto)

service ModbusGateway {
  rpc ReadTags(ReadTagsRequest) returns (ReadTagsResponse);     // 캐시 응답
  rpc WriteTag(WriteTagRequest) returns (WriteTagResponse);     // Modbus write
  rpc StreamTags(StreamTagsRequest) returns (stream TagValue);  // 실시간 구독
  rpc ListTags(ListTagsRequest) returns (ListTagsResponse);     // 메타정보
  rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse);
}
  • ReadTags: 캐시에서 즉시 응답 (sub-millisecond)
  • WriteTag: Modbus FC16 직접 write 후 캐시 갱신
  • StreamTags: 1초 poll 주기에 맞춰 push

레지스터 맵 로딩

  • 시작 시 register-map.json 파일 읽음 (174 KB)
  • 또는 hc900_map_master DB 테이블에서 읽음

3. hc900_map_master (DB 기반 태그 카탈로그)

개념

  • node_map_master와 동일한 패턴
  • HC Designer CSV 3종을 DB에 로드
  • 태그 prefix 기반 분류 (기존 PidPrefixRule 재활용)

DDL

CREATE TABLE hc900_map_master (
    id          SERIAL PRIMARY KEY,
    tag_name    TEXT NOT NULL,       -- 레지스터 맵 태그명 (e.g. 'FICQ3101.PV', 'FIQ6101')
    base_tag    TEXT NOT NULL,       -- 베이스 태그 (e.g. 'FICQ3101', 'FIQ6101')
    attribute   TEXT,                -- 속성 (e.g. 'PV', 'SP', 'QV') — NULL이면 베이스 태그 자체
    addr        INTEGER NOT NULL,    -- Modbus 주소
    count       INTEGER NOT NULL,    -- 레지스터 수 (1 or 2)
    type        TEXT NOT NULL,       -- 'float32' or 'uint16'
    access      TEXT NOT NULL,       -- 'R' or 'RW'
    description TEXT,
    eu          TEXT,
    category    TEXT,                -- PidPrefixRule 기반 분류 ('instrument', 'power_equipment', ...)
    tag_class   TEXT,                -- 'field' or 'system'
    tag_dcs     BOOLEAN,             -- DCS function block 여부
    source      TEXT NOT NULL,       -- 'loop', 'signal', 'variable'
    group_name  TEXT,                -- PointBuilder 그룹 ('Controller1', 'Custom', ...)
    created_at  TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_hc900_map_base ON hc900_map_master(base_tag);
CREATE INDEX idx_hc900_map_addr  ON hc900_map_master(addr);
CREATE INDEX idx_hc900_map_group ON hc900_map_master(group_name);

데이터 출처

CSV 파일 source 건수 범위
SummaryFunctionBlockReport.csv loop 32 loops × 25 params = 800 0x0040~0x7FFF
SignalTags.csv signal 530 0x2000~0x25E4
Variables.csv variable 155 0x18C0~0x1A10

Prefix Rules (PidPrefixRule 활용)

Prefix Category 예시
FIC, FICA, FICQ instrument, tag_dcs=true FICQ3101 (PID Loop)
FIT, FIQ, FT instrument, tag_dcs=false FIT6101, FIQ6101 (Signal Tag)
TI, TIC, TICA instrument, tag_dcs=true TICA6111A, TICA3202A
LI, LIC, LICA instrument LICA6213, LICA5113
PI, PIC, PICA instrument PICA6111
XV instrument XV3208B_REM
VP, P- power_equipment VP8117_HS, P-3101B_RUN

PointBuilder-like Selection

웹 UI에서 hc900_map_master를 대상으로:

Group 필터 조건 용도
Controller1 prefix=FIC/TIC/PIC/LIC, attribute=PV/SP/OP 주요 PID 값
Custom user-defined QV, TRIP, ESD, HS 등

DB 조회 예시:

-- FICQ-6101 계열 모든 태그
SELECT * FROM hc900_map_master
WHERE base_tag IN (
  SELECT base_tag FROM hc900_map_master
  WHERE base_tag ILIKE 'ficq6101'
);

4. OPC UA 이름 매핑 (핵심 난제)

문제

  • DB (Experion OPC UA): ficq-6101.pv, ficq-6101.qv (lowercase, dash, dot attribute)
  • HC Designer CSV: FICQ3101, FIQ6101 (uppercase, no dash, no prefix-dot suffix)
  • R530이 중간에서 이름 변환 + 서브 속성 구성하며, 이 매핑 정보는 R530 설정에만 존재
OPC UA (DB)              HC Designer (CSV)       Modbus
────────────────────     ──────────────────      ───────
ficq-6101.pv             FICA6101 (Loop #11)     0x0A40  ← prefix 다름 (FICQ vs FICA)
ficq-6101.qv.value       FIQ6101 (Signal Tag)    0x2006  ← 사용자 정의 규칙 (FIQ=QV)
ficq-6101.sp             FICA6101 (Loop #11)     0x0A44
ficq-3101.pv             FICQ3101 (Loop #1)      0x0040
ficq-3101.qv.value       FIQ3101 (Signal Tag)    0x2164

prefix 예: FICQ/FICA/FIT/FIQ 모두 숫자부 6101 공유 → prefix는 접근 방식(loop, PV, QV)을 나타내고, 숫자부가 실제 연결고리

매핑 전략: 숫자부 조인 + prefix 검증

realtime_table의 OPC UA 이름과 hc900_map_master의 HC Designer 이름을 숫자부(6101) 기준으로 매칭하고, prefix rules로 검증:

-- Phase A: 후보 추출 (숫자부 기반 매칭)
WITH rt_base AS (
  SELECT DISTINCT
    split_part(tagname, '.', 1) AS opcua_tag,
    tagname,
    substring(tagname FROM '(\d+)') AS tag_number
  FROM realtime_table
),
hc_base AS (
  SELECT
    tag_name,
    base_tag,
    addr,
    substring(tag_name FROM '(\d+)') AS tag_number
  FROM hc900_map_master
)
SELECT
  r.opcua_tag,
  r.tagname      AS opcua_name,
  h.tag_name     AS hc_name,
  h.addr
FROM rt_base r
JOIN hc_base h ON r.tag_number = h.tag_number
  AND r.opcua_tag ~* h.base_tag  -- base_tag regex 매칭
ORDER BY r.opcua_tag, h.addr;

결과물: opcua_aliases

ALTER TABLE hc900_map_master ADD COLUMN opcua_aliases TEXT[];

-- 예시 (자동 생성 후 사용자 검증 필요):
-- FIQ6101       → {"ficq-6101.qv", "ficq-6101.qv.value"}
-- FICQ3101.PV   → {"ficq-3101.pv"}
-- FICA6101-WSP  → {"ficq-6101.wsp"}

사용자 검증 플로우

CSV → hc900_map_master    realtime_table (OPC UA)
         │                       │
         └──── SQL 숫자부 조인 ───┘
                  │
            alias 후보 목록
                  │
            사용자 검증 (Y/N)
                  │
            opcua_aliases 확정
                  │
            C# 앱이 gRPC 호출 시
            aliases로 주소 조회

C# 앱에서 gRPC 요청 시:

// C# 앱은 DB 이름(ficq-6101.pv)으로 요청
// 게이트웨이는 aliases 매칭으로 레지스터 주소 조회
var resp = client.ReadTags(new[] { "ficq-6101.pv", "ficq-6101.qv" });

5. 구현 단계

Phase 1: C++ 게이트웨이 (완성)

  • industrial-comm에 gRPC 서버 추가
  • register-map.json 로더
  • Poller (full poll 1초)
  • gRPC 서비스 구현 (ReadTags, WriteTag, ListTags, HealthCheck)
  • CMakeLists.txt에 gRPC/Protobuf 종속성 추가
  • systemd unit file

Phase 2: DB 및 백엔드

  • hc900_map_master 테이블 생성 SQL
  • build_hc900_map.py CSV → DB 로더 (Python)
  • scripts/build_register_map.py → DB 직접 적재
  • Prefix Rules 추가 (FIQ, FIT, FICA 등)
  • PointBuilder-like 웹 UI (기존 ExperionCrawler UI 확장)

Phase 3: C# HC900Crawler

  • ExperionCrawler 포크 → gRPC Client 추가
  • 기존 OPC UA 계층 대체 (IExperionOpcClient → IModbusGatewayClient)
  • Hc900RealtimeService (캐시 폴링 기반)
  • Write 서비스
  • 기존 DB 스키마 재활용 (realtime_table, history_table, etc.)

Phase 4: OPC UA 이름 매핑

  • node_map_master 조회로 실제 OPC UA 이름-레지스터 관계 분석
  • opcua_aliases DB 구축
  • 매핑 검증 툴

6. 검증된 사실

항목 상태
HC900 Modbus TCP 연결 C70, port 502, IP=192.168.0.240
Float 포맷 FP_B (IEEE 754 big-endian)
Loop 주소 체계 Loop #N = 0x40+(N-1)*0x100 (124), 0x7840+(N-25)*0x100 (2532)
batch read 성능 120 regs = 2.4ms, full poll = 117ms
C70 최대 연결 10개
R530 사용 연결 1개 (Modbus TCP 채널)
우리 사용 가능 9개, 실제론 1~2개면 충분
Signal Tag FIQ6101 = QV ficq-6101.qv.value = FIQ6101 @ 0x2006
사용자 정의 네이밍 FIT=PV, FIQ=QV 등 prefix 기반 규칙 존재

7. 오픈 이슈

  • OPC UA ↔ Modbus 주소 매칭 (최대 난제) — 숫자부 조인으로 후보 추출 후 사용자 검증 필요
  • HC900 Custom Map/User Defined 영역 태그 확인 (현재는 Fixed Map만 커버)
  • Write 시 Modbus FC16 테스트 필요
  • 캐시 무효화 전략 (C#에서 Write 후 gateway cache 갱신)
  • StreamTags 구현 방식 (1초 poll마다 변경분 push vs 전부 push)