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>
148 lines
3.2 KiB
C++
148 lines
3.2 KiB
C++
#include "controller.hpp"
|
|
#include "codec.hpp"
|
|
#include "modbus_tcp.hpp"
|
|
#include <iostream>
|
|
|
|
Controller::Controller()
|
|
: connected_(false)
|
|
{
|
|
}
|
|
|
|
// ---- connection management ------------------------------------
|
|
bool Controller::connect(const std::string& address)
|
|
{
|
|
transport_ = std::make_unique<ModbusTCP>();
|
|
|
|
if (!transport_->connect(address.c_str(), 502)) {
|
|
return false;
|
|
}
|
|
|
|
connected_ = true;
|
|
address_ = address;
|
|
return true;
|
|
}
|
|
|
|
void Controller::disconnect()
|
|
{
|
|
if (connected_) {
|
|
std::cout << "Disconnected from controller at " << address_ << std::endl;
|
|
}
|
|
|
|
connected_ = false;
|
|
address_.clear();
|
|
}
|
|
|
|
bool Controller::is_connected() const
|
|
{
|
|
return connected_;
|
|
}
|
|
|
|
|
|
// ---- PLC-style raw register API ----------------------------------
|
|
|
|
bool Controller::read_register(std::uint16_t address,
|
|
std::uint16_t& value)
|
|
{
|
|
std::vector<std::uint16_t> regs;
|
|
if (!transport_->read_registers(address, 1, regs)) {
|
|
return false;
|
|
}
|
|
|
|
value = regs[0];
|
|
return true;
|
|
}
|
|
|
|
|
|
bool Controller::write_register(std::uint16_t address,
|
|
std::uint16_t value)
|
|
{
|
|
return transport_->write_registers(address, { value });
|
|
}
|
|
|
|
// ---- PLC-style float API ------------------------⭐
|
|
|
|
bool Controller::read_float(std::uint16_t address,
|
|
float& value,
|
|
const DataFormat& fmt)
|
|
{
|
|
if (!connected_) {
|
|
return false;
|
|
}
|
|
|
|
std::vector<std::uint16_t> regs;
|
|
if (!transport_->read_registers(address, 2, regs)) {
|
|
return false;
|
|
}
|
|
|
|
if (regs.size() != 2) {
|
|
return false;
|
|
}
|
|
|
|
value = decode_float(regs[0], regs[1], fmt);
|
|
return true;
|
|
}
|
|
|
|
bool Controller::write_float(std::uint16_t address,
|
|
float value,
|
|
const DataFormat& fmt)
|
|
{
|
|
if (!connected_) {
|
|
return false;
|
|
}
|
|
|
|
// float → 2 registers
|
|
auto regs = encode_float(value, fmt);
|
|
|
|
std::vector<std::uint16_t> values = {
|
|
regs[0], regs[1]
|
|
};
|
|
|
|
return transport_->write_registers(address, values);
|
|
}
|
|
|
|
// ---- PLC-style double API ----------------------------------
|
|
|
|
bool Controller::read_double(std::uint16_t address,
|
|
double& value,
|
|
const DataFormat& fmt)
|
|
{
|
|
if (!connected_) {
|
|
return false;
|
|
}
|
|
|
|
// read 4 consecutive registers atomically
|
|
std::vector<std::uint16_t> regs;
|
|
if (!transport_->read_registers(address, 4, regs)) {
|
|
return false;
|
|
}
|
|
|
|
if (regs.size() != 4) {
|
|
return false;
|
|
}
|
|
|
|
value = decode_double(regs[0],
|
|
regs[1],
|
|
regs[2],
|
|
regs[3],
|
|
fmt);
|
|
return true;
|
|
}
|
|
|
|
bool Controller::write_double(std::uint16_t address,
|
|
double value,
|
|
const DataFormat& fmt)
|
|
{
|
|
if (!connected_) {
|
|
return false;
|
|
}
|
|
|
|
// double → 4 registers
|
|
auto regs = encode_double(value, fmt);
|
|
|
|
std::vector<std::uint16_t> values = {
|
|
regs[0], regs[1], regs[2], regs[3]
|
|
};
|
|
|
|
return transport_->write_registers(address, values);
|
|
}
|