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

173 lines
8.0 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.
# 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_gateway` binary)
- **`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 tools
- **`scripts/`** + **`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).
```bash
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:
```bash
./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)
```bash
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:
```bash
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:
```bash
python3 scripts/load_state_labels.py
```
### Test Utilities
```bash
# 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.json` at startup into `registers_` (vector of `RegisterEntry`) and `tag_index_` (name→index map)
- Spawns a poll thread (`PollLoop`) that runs `ReadAllRegisters()` every `poll_interval_ms`
- `ReadAllRegisters()` groups consecutive registers into batches of ≤120 and issues one `read_raw()` call per batch (~48 batches total for a full HC900 config, ~117 ms round-trip)
- Cache (`cache_`, protected by `cache_mutex_`) stores `CachedValue` per tag; quality=192=good, quality=0=bad/stale
- `transport_mutex_` serializes all Modbus transport calls between the poll thread and gRPC `WriteTag` handlers
**Key gRPC operations** (all implemented in `gateway.cpp`):
- `ReadTags` — reads from cache, sub-millisecond, no Modbus I/O
- `WriteTag` — calls Modbus FC16 directly, then updates cache
- `StreamTags` — pushes cache snapshot at requested interval
- `ListTags` — returns metadata from in-memory register list
- `HealthCheck` — 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_table` upsert (500 rows/batch). 상태 노출: `IsConnected`, `PollCount`, `LastPollAt`
- **`Hc900HistoryService`** — 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 HealthCheck
- `GET /api/gateway/tags` — ListTags
- `POST /api/gateway/write` — WriteTag
- `GET /api/gateway/status` — Hc900RealtimeService 상태
- `GET /api/realtime/points` — realtime_table
- `POST /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 using `LOOP_PARAM_OFFSETS` in `build_register_map.py`. Loop #N base address = `0x40 + (N-1)*0x100` (loops 124), `0x7840 + (N-25)*0x100` (loops 2532).
- **Signal Tags** (read-only): `SignalTags.csv`, addresses `0x20000x25E4`
- **Variables** (R/W): `Variables.csv`, addresses `0x18C00x1A10`
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