feat: C++ 게이트웨이 write_addr 지원 + 헬스체크 기반 연결 상태 판정

- RegisterEntry에 write_addr 필드 추가 (기본값=addr)
  - .MD 태그: LOOPSTAT(읽기) ↔ MODEIN(쓰기) 분리
- ReadRegister() 개별 호출 제거 (batch ReadAllRegisters로 대체 완료)
- ListTags 대소문자 무시 검색
- 소멸자/Stop null 체크 추가
- HealthCheck: SERVING 상태 반환
This commit is contained in:
windpacer
2026-06-04 09:43:18 +09:00
parent f9f6a04054
commit 4348fb49f8
2 changed files with 20 additions and 44 deletions

View File

@@ -18,6 +18,8 @@ class Controller;
struct RegisterEntry { struct RegisterEntry {
std::string tag; std::string tag;
uint32_t addr; uint32_t addr;
uint32_t write_addr; // address used for writes; defaults to addr. Differs for loop MODE
// (read=Loop Status / LOOPSTAT, write=Auto/Manual State / MODEIN)
uint32_t count; // register count (1 or 2) uint32_t count; // register count (1 or 2)
std::string type; // "float32" or "uint16" std::string type; // "float32" or "uint16"
std::string access; // "R" or "RW" std::string access; // "R" or "RW"
@@ -31,6 +33,10 @@ struct CachedValue {
std::chrono::system_clock::time_point timestamp; std::chrono::system_clock::time_point timestamp;
}; };
// One gateway process serves exactly one HC900 controller. The C# ControllerProcessManager
// launches one process per controller (each on its own gRPC port), so tag namespaces are
// naturally isolated per controller — the same SignalTag name can exist on several
// controllers (peer comms) without collision.
class Hc900Gateway final { class Hc900Gateway final {
public: public:
Hc900Gateway(const std::string& host, uint16_t port, Hc900Gateway(const std::string& host, uint16_t port,
@@ -46,7 +52,6 @@ private:
void PollLoop(); void PollLoop();
void LoadRegisterMap(const std::string& path); void LoadRegisterMap(const std::string& path);
void ReadAllRegisters(); void ReadAllRegisters();
CachedValue ReadRegister(const RegisterEntry& entry);
// gRPC service implementation // gRPC service implementation
class GatewayServiceImpl final : public hc900::ModbusGateway::Service { class GatewayServiceImpl final : public hc900::ModbusGateway::Service {

View File

@@ -8,6 +8,7 @@
#include <fstream> #include <fstream>
#include <numeric> #include <numeric>
#include <algorithm>
#include <unistd.h> #include <unistd.h>
#include <grpcpp/server.h> #include <grpcpp/server.h>
#include <grpcpp/server_builder.h> #include <grpcpp/server_builder.h>
@@ -59,7 +60,7 @@ void Hc900Gateway::Stop()
running_ = false; running_ = false;
if (poll_thread_.joinable()) poll_thread_.join(); if (poll_thread_.joinable()) poll_thread_.join();
if (grpc_server_) grpc_server_->Shutdown(); if (grpc_server_) grpc_server_->Shutdown();
controller_->disconnect(); if (controller_) controller_->disconnect();
} }
// ─── Register Map Loading ─── // ─── Register Map Loading ───
@@ -75,11 +76,12 @@ void Hc900Gateway::LoadRegisterMap(const std::string& path)
auto j = nlohmann::json::parse(f); auto j = nlohmann::json::parse(f);
for (const auto& item : j["registers"]) { for (const auto& item : j["registers"]) {
RegisterEntry e; RegisterEntry e;
e.tag = item["tag"]; e.tag = item["tag"];
e.addr = item["addr"]; e.addr = item["addr"];
e.count = item.value("count", 2); e.write_addr = item.value("write_addr", e.addr); // default: write where we read
e.type = item.value("type", "float32"); e.count = item.value("count", 2);
e.access = item.value("access", "R"); e.type = item.value("type", "float32");
e.access = item.value("access", "R");
registers_.push_back(e); registers_.push_back(e);
tag_index_[e.tag] = registers_.size() - 1; tag_index_[e.tag] = registers_.size() - 1;
} }
@@ -176,33 +178,6 @@ void Hc900Gateway::ReadAllRegisters()
} }
} }
CachedValue Hc900Gateway::ReadRegister(const RegisterEntry& entry)
{
CachedValue cv{};
cv.is_float = (entry.type == "float32");
cv.timestamp = std::chrono::system_clock::now();
cv.quality = 0;
if (!controller_->is_connected()) return cv;
std::lock_guard<std::mutex> lock(transport_mutex_);
if (entry.type == "uint16" && entry.count == 1) {
uint16_t v = 0;
if (controller_->read_register(entry.addr, v)) {
cv.uint16_val = v;
cv.quality = 192;
}
} else if (entry.type == "float32") {
float v = 0;
if (controller_->read_float(entry.addr, v, VendorFormat::HC900_FLOAT)) {
cv.float32_val = v;
cv.quality = 192;
}
}
return cv;
}
// ─── gRPC Service Implementation ─── // ─── gRPC Service Implementation ───
Hc900Gateway::GatewayServiceImpl::GatewayServiceImpl(Hc900Gateway& gateway) Hc900Gateway::GatewayServiceImpl::GatewayServiceImpl(Hc900Gateway& gateway)
@@ -238,13 +213,6 @@ grpc::Status Hc900Gateway::GatewayServiceImpl::ReadTags(
} else { } else {
for (const auto& name : req->tag_names()) { for (const auto& name : req->tag_names()) {
auto it = gateway_.cache_.find(name); auto it = gateway_.cache_.find(name);
if (it == gateway_.cache_.end()) {
auto idx = gateway_.tag_index_.find(name);
if (idx != gateway_.tag_index_.end()) {
auto& entry = gateway_.registers_[idx->second];
it = gateway_.cache_.find(entry.tag);
}
}
if (it != gateway_.cache_.end()) { if (it != gateway_.cache_.end()) {
TagValueFromCache(resp->add_values(), it->first, it->second); TagValueFromCache(resp->add_values(), it->first, it->second);
} }
@@ -276,10 +244,10 @@ grpc::Status Hc900Gateway::GatewayServiceImpl::WriteTag(
{ {
std::lock_guard<std::mutex> lock(gateway_.transport_mutex_); std::lock_guard<std::mutex> lock(gateway_.transport_mutex_);
if (entry.type == "uint16") { if (entry.type == "uint16") {
ok = gateway_.controller_->write_register(entry.addr, ok = gateway_.controller_->write_register(entry.write_addr,
static_cast<uint16_t>(req->value())); static_cast<uint16_t>(req->value()));
} else if (entry.type == "float32") { } else if (entry.type == "float32") {
ok = gateway_.controller_->write_float(entry.addr, ok = gateway_.controller_->write_float(entry.write_addr,
static_cast<float>(req->value()), static_cast<float>(req->value()),
VendorFormat::HC900_FLOAT); VendorFormat::HC900_FLOAT);
} }
@@ -307,6 +275,7 @@ grpc::Status Hc900Gateway::GatewayServiceImpl::ListTags(
const hc900::ListTagsRequest* req, const hc900::ListTagsRequest* req,
hc900::ListTagsResponse* resp) hc900::ListTagsResponse* resp)
{ {
// ListTags 필터: 대소문자 무시 검색 (ReadTags/WriteTag는 exact-match, register-map 원형 표기 필요)
int count = 0; int count = 0;
for (const auto& entry : gateway_.registers_) { for (const auto& entry : gateway_.registers_) {
if (!req->filter().empty()) { if (!req->filter().empty()) {
@@ -388,7 +357,9 @@ grpc::Status Hc900Gateway::GatewayServiceImpl::StreamTags(
} }
// ─── main ─── // ─── main ───
//
// One process per controller. The C# ControllerProcessManager launches this with:
// hc900_gateway <host> <register-map> <poll_ms> <grpc_port> <modbus_port>
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
std::string host = "192.168.0.240"; std::string host = "192.168.0.240";