2026-02-02 23:24:24 +08:00

509 lines
16 KiB
C++

#include "PLCModbusClient.h"
#include "VrLog.h"
#include <cstring>
#include <chrono>
PLCModbusClient::PLCModbusClient()
: m_plcClient(nullptr)
, m_plcPort(502)
, m_lastPhotoRequestState(false)
, m_lastConnectedState(false)
, m_pollRunning(false)
, m_photoRequestPaused(false)
, m_pollIntervalMs(1000)
, m_shutdownRequested(false)
, m_autoReconnect(true)
, m_reconnectInterval(2000)
, m_plcReconnectAttempts(0)
{
}
PLCModbusClient::~PLCModbusClient()
{
Shutdown();
}
bool PLCModbusClient::Initialize(const std::string& plcIP, int plcPort)
{
RegisterConfig regConfig;
return Initialize(plcIP, plcPort, regConfig);
}
bool PLCModbusClient::Initialize(const std::string& plcIP, int plcPort, const RegisterConfig& regConfig)
{
// 如果已经在运行,先停止
if (m_pollRunning) {
StopPolling();
}
m_plcIP = plcIP;
m_plcPort = plcPort;
m_registerConfig = regConfig;
m_shutdownRequested = false;
m_plcReconnectAttempts = 0;
m_lastPhotoRequestState = false;
m_lastConnectedState = false;
LOG_INFO("Link PLC ip : %s, port : %d\n", plcIP.c_str(), plcPort);
LOG_INFO("PLCModbusClient Initialize: PhotoRequest=%d, DataComplete=%d, CoordStart=%d\n",
m_registerConfig.addrPhotoRequest, m_registerConfig.addrDataComplete,
m_registerConfig.addrCoordDataStart);
bool connected = false;
bool createFailed = false;
// 创建 PLC 客户端并连接
if (!plcIP.empty()) {
std::lock_guard<std::mutex> locker(m_mutex);
// 清理旧的客户端
if (m_plcClient) {
m_plcClient->Disconnect();
delete m_plcClient;
m_plcClient = nullptr;
}
if (!IYModbusTCPClient::CreateInstance(&m_plcClient, plcIP, plcPort)) {
LOG_ERROR("Failed to create PLC Modbus client\n");
createFailed = true;
} else {
m_plcClient->SetTimeout(1000);
auto result = m_plcClient->Connect();
if (result != IYModbusTCPClient::SUCCESS) {
LOG_WARNING("Failed to connect to PLC at %s:%d, error: %s\n",
m_plcIP.c_str(), m_plcPort, m_plcClient->GetLastError().c_str());
} else {
LOG_INFO("Connected to PLC at %s:%d\n", m_plcIP.c_str(), m_plcPort);
connected = true;
}
}
}
// 在锁外调用回调,避免死锁
if (createFailed) {
notifyError("PLC客户端创建失败");
}
m_lastConnectedState = connected;
notifyConnectionStateChanged(connected);
return true;
}
void PLCModbusClient::Shutdown()
{
// 防止重复调用
bool expected = false;
if (!m_shutdownRequested.compare_exchange_strong(expected, true)) {
return;
}
LOG_INFO("PLCModbusClient shutdown requested\n");
// 停止轮询线程
StopPolling();
// 清理 PLC 客户端
{
std::lock_guard<std::mutex> locker(m_mutex);
if (m_plcClient) {
m_plcClient->Disconnect();
delete m_plcClient;
m_plcClient = nullptr;
}
}
LOG_INFO("PLCModbusClient shutdown complete\n");
}
void PLCModbusClient::StartPolling(int intervalMs)
{
if (m_shutdownRequested) {
LOG_WARNING("Cannot start polling: shutdown requested\n");
return;
}
if (m_pollRunning) {
LOG_WARNING("Polling already running\n");
return;
}
m_pollIntervalMs = intervalMs;
m_pollRunning = true;
m_pollThread = std::thread(&PLCModbusClient::pollThreadFunc, this);
LOG_INFO("PLC polling started, interval: %d ms\n", intervalMs);
}
void PLCModbusClient::StopPolling()
{
if (!m_pollRunning) {
return;
}
m_pollRunning = false;
if (m_pollThread.joinable()) {
m_pollThread.join();
}
LOG_INFO("PLC polling stopped\n");
}
void PLCModbusClient::SetPhotoTriggerCallback(PhotoTriggerCallback callback)
{
std::lock_guard<std::mutex> lock(m_callbackMutex);
m_photoCallback = callback;
}
void PLCModbusClient::SetConnectionStateCallback(ConnectionStateCallback callback)
{
std::lock_guard<std::mutex> lock(m_callbackMutex);
m_connectionStateCallback = callback;
}
void PLCModbusClient::SetErrorCallback(ErrorCallback callback)
{
std::lock_guard<std::mutex> lock(m_callbackMutex);
m_errorCallback = callback;
}
void PLCModbusClient::SetReconnectingCallback(ReconnectingCallback callback)
{
std::lock_guard<std::mutex> lock(m_callbackMutex);
m_reconnectingCallback = callback;
}
void PLCModbusClient::SetReconnectInterval(int intervalMs)
{
m_reconnectInterval = intervalMs;
}
void PLCModbusClient::SetAutoReconnect(bool enable)
{
m_autoReconnect = enable;
}
void PLCModbusClient::pollThreadFunc()
{
LOG_INFO("Poll thread started\n");
int reconnectCounter = 0;
while (m_pollRunning && !m_shutdownRequested) {
// 检查连接状态
bool currentConnected = checkConnection();
// 连接状态变化时通知
if (currentConnected != m_lastConnectedState) {
m_lastConnectedState = currentConnected;
notifyConnectionStateChanged(currentConnected);
// 重连成功后重置状态
if (currentConnected) {
m_lastPhotoRequestState = false;
m_plcReconnectAttempts = 0;
reconnectCounter = 0;
LOG_INFO("PLC reconnected successfully\n");
}
}
if (!currentConnected) {
// 未连接时,尝试重连
if (m_autoReconnect) {
reconnectCounter++;
int reconnectThreshold = (m_pollIntervalMs > 0) ? (m_reconnectInterval / m_pollIntervalMs) : 30;
if (reconnectCounter >= reconnectThreshold) {
reconnectCounter = 0;
m_plcReconnectAttempts++;
notifyReconnecting("PLC", m_plcReconnectAttempts);
LOG_INFO("Attempting to reconnect PLC (attempt %d)...\n", m_plcReconnectAttempts);
tryConnectPLC();
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(m_pollIntervalMs));
continue;
}
// 已连接,检查是否暂停了拍照请求轮询
if (m_photoRequestPaused) {
// 暂停期间不读取拍照请求,只维持连接
std::this_thread::sleep_for(std::chrono::milliseconds(m_pollIntervalMs));
continue;
}
// 读取拍照请求寄存器
int requestValue = readPhotoRequest();
if (requestValue >= 0) {
// 边沿检测:只在 0->1 变化时触发拍照
bool currentState = (requestValue == 1);
if (currentState && !m_lastPhotoRequestState) {
LOG_INFO("Photo request detected (D1000=1)\n");
notifyPhotoRequested(1);
}
m_lastPhotoRequestState = currentState;
} else {
// 读取失败,可能是连接断开,主动断开连接以触发重连
LOG_WARNING("Failed to read photo request, disconnecting to trigger reconnect\n");
disconnectPLC();
}
std::this_thread::sleep_for(std::chrono::milliseconds(m_pollIntervalMs));
}
LOG_INFO("Poll thread exited\n");
}
bool PLCModbusClient::checkConnection()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_plcClient && m_plcClient->IsConnected();
}
bool PLCModbusClient::tryConnectPLC()
{
std::lock_guard<std::mutex> locker(m_mutex);
if (!m_plcClient) {
if (!IYModbusTCPClient::CreateInstance(&m_plcClient, m_plcIP, m_plcPort)) {
LOG_ERROR("Failed to create PLC Modbus client\n");
return false;
}
m_plcClient->SetTimeout(1000);
}
auto result = m_plcClient->Connect();
if (result != IYModbusTCPClient::SUCCESS) {
LOG_WARNING("Failed to connect to PLC at %s:%d, error: %s\n",
m_plcIP.c_str(), m_plcPort, m_plcClient->GetLastError().c_str());
return false;
}
LOG_INFO("Connected to PLC at %s:%d\n", m_plcIP.c_str(), m_plcPort);
return true;
}
void PLCModbusClient::disconnectPLC()
{
std::lock_guard<std::mutex> locker(m_mutex);
if (m_plcClient) {
m_plcClient->Disconnect();
LOG_INFO("PLC disconnected for reconnection\n");
}
}
int PLCModbusClient::readPhotoRequest()
{
std::lock_guard<std::mutex> locker(m_mutex);
if (!m_plcClient || !m_plcClient->IsConnected()) {
return -1;
}
std::vector<uint16_t> values;
auto result = m_plcClient->ReadHoldingRegisters(m_registerConfig.addrPhotoRequest, 1, values);
if (result != IYModbusTCPClient::SUCCESS || values.empty()) {
return -1;
}
return static_cast<int>(values[0]);
}
bool PLCModbusClient::ClearPhotoRequest()
{
std::lock_guard<std::mutex> locker(m_mutex);
if (!m_plcClient || !m_plcClient->IsConnected()) {
LOG_ERROR("PLC not connected, cannot clear photo request\n");
return false;
}
auto result = m_plcClient->WriteSingleRegister(m_registerConfig.addrPhotoRequest, 0);
if (result != IYModbusTCPClient::SUCCESS) {
LOG_ERROR("Failed to clear photo request (addr %d): %s\n",
m_registerConfig.addrPhotoRequest, m_plcClient->GetLastError().c_str());
return false;
}
LOG_INFO("Cleared photo request (addr %d = 0)\n", m_registerConfig.addrPhotoRequest);
return true;
}
void PLCModbusClient::floatToRegisters(float value, uint16_t& high, uint16_t& low)
{
uint32_t bits;
std::memcpy(&bits, &value, sizeof(float));
high = static_cast<uint16_t>((bits >> 16) & 0xFFFF);
low = static_cast<uint16_t>(bits & 0xFFFF);
}
bool PLCModbusClient::SendCoordinateToPLC(const CoordinateData& coord, int pointIndex)
{
std::lock_guard<std::mutex> locker(m_mutex);
if (!m_plcClient || !m_plcClient->IsConnected()) {
LOG_ERROR("PLC not connected, cannot send coordinate\n");
return false;
}
if (pointIndex < 0 || pointIndex >= MAX_POINTS) {
LOG_ERROR("Invalid point index: %d (valid: 0-%d)\n", pointIndex, MAX_POINTS - 1);
return false;
}
int startAddr = m_registerConfig.addrCoordDataStart + pointIndex * REGS_PER_POINT;
std::vector<uint16_t> registers(REGS_PER_POINT);
floatToRegisters(coord.x, registers[0], registers[1]);
floatToRegisters(coord.y, registers[2], registers[3]);
floatToRegisters(coord.z, registers[4], registers[5]);
floatToRegisters(coord.pitch, registers[6], registers[7]);
floatToRegisters(coord.roll, registers[8], registers[9]);
floatToRegisters(coord.yaw, registers[10], registers[11]);
auto result = m_plcClient->WriteMultipleRegisters(startAddr, registers);
if (result != IYModbusTCPClient::SUCCESS) {
LOG_ERROR("Failed to write coordinate to PLC (addr %d): %s\n",
startAddr, m_plcClient->GetLastError().c_str());
return false;
}
LOG_INFO("Sent point[%d] (addr %d): X=%.3f, Y=%.3f, Z=%.3f, P=%.3f, R=%.3f, Y=%.3f\n",
pointIndex, startAddr, coord.x, coord.y, coord.z, coord.pitch, coord.roll, coord.yaw);
return true;
}
bool PLCModbusClient::SendCoordinatesToPLC(const std::vector<CoordinateData>& coords)
{
std::lock_guard<std::mutex> locker(m_mutex);
if (!m_plcClient || !m_plcClient->IsConnected()) {
LOG_ERROR("PLC not connected, cannot send coordinates\n");
return false;
}
int pointCount = std::min(static_cast<int>(coords.size()), MAX_POINTS);
if (pointCount == 0) {
LOG_WARNING("No coordinates to send\n");
return true;
}
std::vector<uint16_t> registers(pointCount * REGS_PER_POINT);
for (int i = 0; i < pointCount; i++) {
const CoordinateData& coord = coords[i];
int offset = i * REGS_PER_POINT;
floatToRegisters(coord.x, registers[offset + 0], registers[offset + 1]);
floatToRegisters(coord.y, registers[offset + 2], registers[offset + 3]);
floatToRegisters(coord.z, registers[offset + 4], registers[offset + 5]);
floatToRegisters(coord.roll, registers[offset + 6], registers[offset + 7]);
floatToRegisters(coord.pitch, registers[offset + 8], registers[offset + 9]);
floatToRegisters(coord.yaw, registers[offset + 10], registers[offset + 11]);
}
auto result = m_plcClient->WriteMultipleRegisters(m_registerConfig.addrCoordDataStart, registers);
if (result != IYModbusTCPClient::SUCCESS) {
LOG_ERROR("Failed to write coordinates to PLC (addr %d): %s\n",
m_registerConfig.addrCoordDataStart, m_plcClient->GetLastError().c_str());
return false;
}
LOG_INFO("Sent %d points to PLC (addr %d-%d)\n",
pointCount, m_registerConfig.addrCoordDataStart,
m_registerConfig.addrCoordDataStart + pointCount * REGS_PER_POINT - 1);
return true;
}
bool PLCModbusClient::NotifyDataComplete()
{
std::lock_guard<std::mutex> locker(m_mutex);
if (!m_plcClient || !m_plcClient->IsConnected()) {
LOG_ERROR("PLC not connected, cannot notify data complete\n");
return false;
}
auto result = m_plcClient->WriteSingleRegister(m_registerConfig.addrDataComplete, 1);
if (result != IYModbusTCPClient::SUCCESS) {
LOG_ERROR("Failed to notify data complete (addr %d): %s\n",
m_registerConfig.addrDataComplete, m_plcClient->GetLastError().c_str());
return false;
}
LOG_INFO("Notified data complete (addr %d = 1)\n", m_registerConfig.addrDataComplete);
return true;
}
bool PLCModbusClient::IsPLCConnected() const
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_plcClient && m_plcClient->IsConnected();
}
void PLCModbusClient::PausePhotoRequestPolling()
{
m_photoRequestPaused = true;
LOG_INFO("Photo request polling paused\n");
}
void PLCModbusClient::ResumePhotoRequestPolling()
{
m_photoRequestPaused = false;
LOG_INFO("Photo request polling resumed\n");
}
bool PLCModbusClient::IsPhotoRequestPollingPaused() const
{
return m_photoRequestPaused;
}
// ========== 安全的回调通知(先复制回调,释放锁后再调用)==========
void PLCModbusClient::notifyConnectionStateChanged(bool connected)
{
ConnectionStateCallback callback;
{
std::lock_guard<std::mutex> lock(m_callbackMutex);
callback = m_connectionStateCallback;
}
if (callback) {
callback(connected);
}
}
void PLCModbusClient::notifyError(const std::string& errorMsg)
{
ErrorCallback callback;
{
std::lock_guard<std::mutex> lock(m_callbackMutex);
callback = m_errorCallback;
}
if (callback) {
callback(errorMsg);
}
}
void PLCModbusClient::notifyPhotoRequested(int cameraIndex)
{
PhotoTriggerCallback callback;
{
std::lock_guard<std::mutex> lock(m_callbackMutex);
callback = m_photoCallback;
}
if (callback) {
callback(cameraIndex);
}
}
void PLCModbusClient::notifyReconnecting(const std::string& device, int attempt)
{
ReconnectingCallback callback;
{
std::lock_guard<std::mutex> lock(m_callbackMutex);
callback = m_reconnectingCallback;
}
if (callback) {
callback(device, attempt);
}
}