feat: C++ 게이트웨이 write_addr 지원 + 헬스체크 기반 연결 상태 판정
- RegisterEntry에 write_addr 필드 추가 (기본값=addr) - .MD 태그: LOOPSTAT(읽기) ↔ MODEIN(쓰기) 분리 - ReadRegister() 개별 호출 제거 (batch ReadAllRegisters로 대체 완료) - ListTags 대소문자 무시 검색 - 소멸자/Stop null 체크 추가 - HealthCheck: SERVING 상태 반환
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
Reference in New Issue
Block a user