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>
8.0 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
This project replaces the Experion OPC UA data path with a direct Modbus TCP connection to the Honeywell HC900 process controller:
Before: HC900 ──Modbus TCP──▶ Experion R530 ──OPC UA──▶ ExperionCrawler ──▶ PostgreSQL
After: HC900 ──Modbus TCP──▶ C++ Gateway ──gRPC──▶ HC900Crawler ──▶ PostgreSQL
Four active components:
industrial-comm/cpp/— C++ gateway: Modbus TCP poller + gRPC server (hc900_gatewaybinary)src/Hc900Crawler/— C# .NET 8 ASP.NET Core web app: full monitoring platform (gRPC client + web UI + KB/P&ID/FF)mcp-server/— Python FastMCP server (port 5001): RAG, NL2SQL, P&ID processing toolsscripts/+test/— Python utilities for register map generation and testing
Commands
C++ Gateway (build)
gRPC and abseil are pre-installed to /tmp/grpc_local and /tmp/absl_local (aarch64).
cd industrial-comm/cpp
mkdir -p build && cd build
cmake ..
make -j$(nproc)
Produces build/hc900_gateway and build/libcomm_core.so.
Run the gateway:
./build/hc900_gateway [host] [register-map-path] [poll_ms]
# defaults: 192.168.0.240, docs/register-map.json, 1000
Log file: /tmp/hc900_gateway.log. gRPC listens on 0.0.0.0:50051.
C# Crawler (build + run)
cd src/Hc900Crawler
dotnet build
dotnet run
Configuration via appsettings.json: Hc900.GatewayAddress (default http://localhost:50051), Hc900.PollIntervalMs, ConnectionStrings.DefaultConnection (PostgreSQL, Search Path=hc900). Serves web UI at http://0.0.0.0:5000.
Register Map Generation
Converts HC Designer CSV exports → docs/register-map.json used by the gateway at startup:
python3 scripts/build_register_map.py \
--loop-csv docs/SummaryFucntionBlockReport.csv \
--signal-csv docs/SignalTags.csv \
--variable-csv docs/Variables.csv \
-o docs/register-map.json
Load state labels (StatusPoint descriptors from xlsx) into DB:
python3 scripts/load_state_labels.py
Test Utilities
# Start Modbus TCP simulator (port 5020, loads register-map.json)
python3 test/modbus_sim.py
# Read tags directly via Modbus TCP
python3 test/read_tags.py FICQ3101.PV FICQ3101.SP FICQ3101.MODE
python3 test/read_tags.py --port 5020 FICQ3101.PV # against simulator
python3 test/read_tags.py --all --limit 50
# pymodbus must be available; it's expected at /tmp/hc900_venv
/tmp/hc900_venv/bin/python3 test/read_tags.py FICQ3101.PV
Architecture
C++ Gateway (industrial-comm/cpp/)
Hc900Gateway (src/gateway.cpp, include/gateway.h) is the core class:
- Loads
register-map.jsonat startup intoregisters_(vector ofRegisterEntry) andtag_index_(name→index map) - Spawns a poll thread (
PollLoop) that runsReadAllRegisters()everypoll_interval_ms ReadAllRegisters()groups consecutive registers into batches of ≤120 and issues oneread_raw()call per batch (~48 batches total for a full HC900 config, ~117 ms round-trip)- Cache (
cache_, protected bycache_mutex_) storesCachedValueper tag; quality=192=good, quality=0=bad/stale transport_mutex_serializes all Modbus transport calls between the poll thread and gRPCWriteTaghandlers
Key gRPC operations (all implemented in gateway.cpp):
ReadTags— reads from cache, sub-millisecond, no Modbus I/OWriteTag— calls Modbus FC16 directly, then updates cacheStreamTags— pushes cache snapshot at requested intervalListTags— returns metadata from in-memory register listHealthCheck— reports connection state, poll count, last poll duration
Controller (src/controller.cpp, include/controller.hpp) wraps ITransport with typed read/write methods. ModbusTCP (src/modbus_tcp.cpp, include/modbus_tcp.hpp) implements the transport.
Codec (src/codec.cpp, include/codec.hpp) handles byte/word order for float32, int32, int64, double. HC900 uses VendorFormat::HC900_FLOAT = {BigEndian, HighFirst, Normal} (FP B format per manual).
Proto: The generated files (gen/modbus_gateway.pb.{cc,h} and gen/modbus_gateway.grpc.pb.{cc,h}) are pre-built and committed. The source proto is at proto/modbus_gateway.proto. The C# copy lives at src/Hc900Crawler/Proto/modbus_gateway.proto and is compiled by MSBuild via Grpc.Tools.
C# Crawler (src/Hc900Crawler/) — 3-Layer Architecture
Project layout (ExperionCrawler 패턴 적용):
src/
Core/ ← Domain entities, interfaces, application services
Infrastructure/ ← DB (Hc900DbContext), HC900 services, Control, Kb, Mcp, Trend, Docs
Hc900Crawler/ ← ASP.NET Core web project (Controllers, wwwroot, Program.cs, csproj)
mcp-server/ ← Python MCP server (copied from ExperionCrawler)
BackgroundServices:
Hc900RealtimeService(Infrastructure/Hc900/) — gRPC 폴링 →hc900.realtime_tableupsert (500 rows/batch). 상태 노출:IsConnected,PollCount,LastPollAtHc900HistoryService— 60초 주기realtime_table→history_table스냅샷 (IsConnected 확인 후 실행)Hc900DigitalEventDetectorService— 1초 주기로 realtime_table 변화 감지 →event_history_table기록
Hc900GatewayClient (Infrastructure/Hc900/) — IHc900GatewayService 구현. gRPC 채널 lazy 생성. GetHealthAsync(), ListTagsAsync(), WriteTagAsync().
Hc900WriteService — FeedforwardSupervisor·FeedforwardController에서 SP 쓰기용.
Value formatting: 상태 레이블 있으면 {N | LABEL | }, 없으면 float/uint16 string.
Web API endpoints (port 5000):
GET /api/gateway/health— gRPC HealthCheckGET /api/gateway/tags— ListTagsPOST /api/gateway/write— WriteTagGET /api/gateway/status— Hc900RealtimeService 상태GET /api/realtime/points— realtime_tablePOST /api/history/query— history_table 조회POST /api/events/query— event_history_table 조회/api/pid/*,/api/kb/*,/api/ff/*,/api/t2s/*,/api/ollama/*등 ExperionCrawler 동일
Database (PostgreSQL, schema hc900)
Hc900DbContext (Infrastructure/Database/Hc900DbContext.cs) — HasDefaultSchema("hc900") + Search Path=hc900 연결문자열. InitializeAsync()에서 모든 테이블·뷰·TimeScaleDB 하이퍼테이블 자동 생성.
| Table | Purpose |
|---|---|
hc900_map_master |
OPC UA tagname ↔ hc900_tag 매핑, Modbus addr, 데이터타입 |
realtime_table |
실시간 값 (tagname, livevalue, timestamp) — upsert on conflict |
history_table |
60초 이력 스냅샷 (TimeScaleDB hypertable) |
event_history_table |
디지털 태그 상태 변경 이벤트 (TRIP/ALARM/RUN 등) |
tag_metadata |
태그 메타 (description, area, sub_area, state0-7 레이블) |
pid_equipment, pid_prefix_rules |
P&ID 추출 데이터 |
kb_* |
Knowledge Base (Qdrant RAG) |
ff_* |
Feedforward 제어 설정/감사 |
Register Map (docs/register-map.json)
JSON file with a top-level registers array. Each entry: tag, addr (0-based Modbus holding register address), count (1=uint16, 2=float32), type, access ("R"/"RW"), description.
Sources:
- Loops (PID):
SummaryFucntionBlockReport.csv→ expanded into per-parameter entries usingLOOP_PARAM_OFFSETSinbuild_register_map.py. Loop #N base address =0x40 + (N-1)*0x100(loops 1–24),0x7840 + (N-25)*0x100(loops 25–32). - Signal Tags (read-only):
SignalTags.csv, addresses0x2000–0x25E4 - Variables (R/W):
Variables.csv, addresses0x18C0–0x1A10
Float format is FP_B (IEEE 754 big-endian, wire order bytes 4,3,2,1).
HC900 Hardware
- Controller: HC900-C70, IP
192.168.0.240, Modbus TCP port 502 - Maximum simultaneous connections: 10 (R530 uses 1, leaving 9 available)
- Unit ID: 0x00 (not used in Modbus TCP mode)
- Float format must be set to FP B on the controller