- build_controllers_config.py: gateway-config.json 생성 - load_map_master.py: hc900_map_master 테이블 적재 - measure_scan_time.py: HC900 스캔 시간 측정 유틸
108 lines
4.6 KiB
Python
108 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Build the C# gateway process config (config/gateway-config.json) from the
|
|
Experion controller export (Controller.csv).
|
|
|
|
The crawler's ControllerProcessManager reads this file and launches one
|
|
``hc900_gateway`` process per enabled controller:
|
|
hc900_gateway <controllerIp> <registerMapPath> <pollIntervalMs> <grpcPort> <controllerPort>
|
|
Each controller therefore gets its own gRPC port and its own register map — the
|
|
same SignalTag name may exist on several controllers (peer comms) without any
|
|
collision, because the namespaces are isolated per process.
|
|
|
|
Controller.csv has several sections (UniversalModbus, OPC UA, Modicon); we keep
|
|
only the HC900 ``UniversalModbusController`` rows and use IPAddress1 (the primary
|
|
192.168.0.x net; IPAddress2 is the redundant link).
|
|
|
|
The existing file's ``shared`` block and any per-controller overrides (grpcPort,
|
|
enabled) are preserved when --merge is given.
|
|
|
|
Usage:
|
|
python3 build_controllers_config.py --csv docs/Controller.csv \
|
|
-o config/gateway-config.json --merge
|
|
"""
|
|
|
|
import csv
|
|
import json
|
|
import argparse
|
|
from pathlib import Path
|
|
|
|
REPO = Path(__file__).resolve().parent.parent
|
|
|
|
DEFAULT_SHARED = {
|
|
"binaryPath": str(REPO / "industrial-comm/cpp/build/hc900_gateway"),
|
|
"ldLibraryPath": "/tmp/grpc_local/usr/lib/aarch64-linux-gnu:/tmp/absl_local/usr/lib/aarch64-linux-gnu",
|
|
"logDir": "/tmp",
|
|
}
|
|
|
|
|
|
def parse_controllers(csv_path: Path):
|
|
"""Yield (name, ip) for each HC900 UniversalModbusController in Controller.csv."""
|
|
header = None
|
|
with open(csv_path, encoding="utf-8-sig") as f:
|
|
for row in csv.reader(f):
|
|
if not row or all(not c.strip() for c in row):
|
|
continue
|
|
if row[0].strip() == "ItemName": # section header
|
|
header = {n.strip(): i for i, n in enumerate(row)}
|
|
continue
|
|
if header is None:
|
|
continue
|
|
if row[header.get("Class", 1)].strip() != "UniversalModbusController":
|
|
continue
|
|
name = row[header["ItemName"]].strip()
|
|
ip = row[header["IPAddress1"]].strip() if "IPAddress1" in header else ""
|
|
if name and ip:
|
|
yield name, ip
|
|
|
|
|
|
def build(csv_path: Path, map_dir: str, map_prefix: str, poll_ms: int,
|
|
base_grpc_port: int, existing: dict | None) -> dict:
|
|
shared = (existing or {}).get("shared") or dict(DEFAULT_SHARED)
|
|
# index existing controllers by id to preserve per-controller overrides
|
|
prev = {c.get("id"): c for c in (existing or {}).get("controllers", [])}
|
|
|
|
controllers = []
|
|
for i, (name, ip) in enumerate(parse_controllers(csv_path)):
|
|
old = prev.get(name, {})
|
|
controllers.append({
|
|
"id": name,
|
|
"name": old.get("name") or f"HC900 {name} Controller",
|
|
"controllerIp": ip,
|
|
"controllerPort": old.get("controllerPort", 502),
|
|
# grpcPort is assigned sequentially to guarantee uniqueness across
|
|
# the per-controller processes (one bind per port).
|
|
"grpcPort": base_grpc_port + i,
|
|
"pollIntervalMs": old.get("pollIntervalMs", poll_ms),
|
|
"registerMapPath": str(REPO / map_dir / f"{map_prefix}{name.lower()}.json"),
|
|
"enabled": old.get("enabled", True),
|
|
})
|
|
return {"shared": shared, "controllers": controllers}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
p = argparse.ArgumentParser(description="Build config/gateway-config.json from Controller.csv")
|
|
p.add_argument("--csv", default="docs/Controller.csv")
|
|
p.add_argument("--map-dir", default="docs")
|
|
p.add_argument("--map-prefix", default="register-map-")
|
|
p.add_argument("--poll-ms", type=int, default=500)
|
|
p.add_argument("--base-grpc-port", type=int, default=50051)
|
|
p.add_argument("-o", "--output", default="config/gateway-config.json")
|
|
p.add_argument("--merge", action="store_true",
|
|
help="preserve shared block and per-controller overrides from the existing file")
|
|
args = p.parse_args()
|
|
|
|
out = Path(args.output)
|
|
existing = None
|
|
if args.merge and out.exists():
|
|
existing = json.loads(out.read_text(encoding="utf-8"))
|
|
|
|
cfg = build(Path(args.csv), args.map_dir, args.map_prefix, args.poll_ms,
|
|
args.base_grpc_port, existing)
|
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
out.write_text(json.dumps(cfg, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
print(f"✓ Wrote {out} ({len(cfg['controllers'])} controllers)")
|
|
for c in cfg["controllers"]:
|
|
en = "" if c["enabled"] else " (disabled)"
|
|
print(f" {c['id']:4s} {c['controllerIp']:16s} grpc:{c['grpcPort']} → {Path(c['registerMapPath']).name}{en}")
|