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>
This commit is contained in:
172
CLAUDE.md
Normal file
172
CLAUDE.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# 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 1–24), `0x7840 + (N-25)*0x100` (loops 25–32).
|
||||
- **Signal Tags** (read-only): `SignalTags.csv`, addresses `0x2000–0x25E4`
|
||||
- **Variables** (R/W): `Variables.csv`, addresses `0x18C0–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
|
||||
Reference in New Issue
Block a user