GrabBag/App/RodWeldSeam/RodWeldSeamApp/Presenter/Src/RodWeldSeamTCPProtocol.cpp

413 lines
12 KiB
C++
Raw Normal View History

2026-07-20 11:52:35 +08:00
#include "RodWeldSeamTCPProtocol.h"
#include "VrLog.h"
#include <QString>
#include <QStringList>
#include <cmath>
#include <exception>
#include <utility>
#include <vector>
namespace {
std::string FrameText(const std::string& text)
{
std::string framed = text;
while (!framed.empty() && (framed.back() == '\r' || framed.back() == '\n')) {
framed.pop_back();
}
framed += "\r\n";
return framed;
}
} // namespace
RodWeldSeamTCPProtocol::RodWeldSeamTCPProtocol() = default;
RodWeldSeamTCPProtocol::~RodWeldSeamTCPProtocol()
{
Deinitialize();
}
int RodWeldSeamTCPProtocol::Initialize(uint16_t port)
{
std::lock_guard<std::mutex> lifecycleLock(m_lifecycleMutex);
if (m_serverRunning.load()) {
LOG_WARNING("RodWeldSeam TCP server is already running on port %u\n",
static_cast<unsigned int>(m_port));
return 0;
}
IYTCPServer* server = nullptr;
if (!VrCreatYTCPServer(&server) || !server) {
LOG_ERROR("Failed to create RodWeldSeam TCP server instance\n");
return -1;
}
if (!server->Init(port)) {
LOG_ERROR("Failed to initialize RodWeldSeam TCP server on port %u\n",
static_cast<unsigned int>(port));
delete server;
return -2;
}
server->SetEventCallback(
[this](const TCPClient* client, TCPServerEventType eventType) {
OnTCPEvent(client, eventType);
});
{
std::lock_guard<std::mutex> sendLock(m_sendMutex);
m_tcpServer = server;
m_port = port;
// Start() 启动接收线程后客户端即可立即发包,先发布运行状态供错误应答使用。
m_serverRunning.store(true);
}
if (!server->Start(
[this](const TCPClient* client, const char* data, const unsigned int length) {
OnTCPDataReceived(client, data, length);
})) {
LOG_ERROR("Failed to start RodWeldSeam TCP server on port %u\n",
static_cast<unsigned int>(port));
{
std::lock_guard<std::mutex> sendLock(m_sendMutex);
m_serverRunning.store(false);
m_tcpServer = nullptr;
}
server->Close();
delete server;
return -3;
}
LOG_INFO("RodWeldSeam TCP server initialized on port %u\n",
static_cast<unsigned int>(port));
return 0;
}
void RodWeldSeamTCPProtocol::Deinitialize()
{
std::lock_guard<std::mutex> lifecycleLock(m_lifecycleMutex);
IYTCPServer* server = nullptr;
{
// 等待已经开始的发送结束,并阻止后续发送再访问 server。
std::lock_guard<std::mutex> sendLock(m_sendMutex);
m_serverRunning.store(false);
server = m_tcpServer;
}
if (server) {
server->Stop();
server->Close();
{
std::lock_guard<std::mutex> sendLock(m_sendMutex);
if (m_tcpServer == server) {
m_tcpServer = nullptr;
}
}
delete server;
}
bool notifyDisconnected = false;
{
std::lock_guard<std::mutex> clientLock(m_clientMutex);
notifyDisconnected = !m_clients.empty();
m_clients.clear();
m_clientBuffers.clear();
m_clientCount.store(0);
}
if (notifyDisconnected) {
NotifyConnectionChanged(false);
}
}
bool RodWeldSeamTCPProtocol::IsRunning() const
{
return m_serverRunning.load();
}
std::size_t RodWeldSeamTCPProtocol::ClientCount() const
{
return m_clientCount.load();
}
void RodWeldSeamTCPProtocol::SetConnectionCallback(const ConnectionCallback& callback)
{
std::lock_guard<std::mutex> callbackLock(m_callbackMutex);
m_connectionCallback = callback;
}
void RodWeldSeamTCPProtocol::SetDetectionTriggerCallback(
const DetectionTriggerCallback& callback)
{
std::lock_guard<std::mutex> callbackLock(m_callbackMutex);
m_detectionTriggerCallback = callback;
}
int RodWeldSeamTCPProtocol::SendTextResult(const std::string& text)
{
const std::string framed = FrameText(text);
std::lock_guard<std::mutex> sendLock(m_sendMutex);
if (!m_serverRunning.load() || !m_tcpServer) {
return -1;
}
const bool sent = m_tcpServer->SendAllData(
framed.c_str(), static_cast<int>(framed.size()));
return sent ? 0 : -2;
}
int RodWeldSeamTCPProtocol::SendTextToClient(
const TCPClient* client, const std::string& text)
{
if (!client) {
return -2;
}
const std::string framed = FrameText(text);
std::lock_guard<std::mutex> sendLock(m_sendMutex);
if (!m_serverRunning.load() || !m_tcpServer) {
return -1;
}
const bool sent = m_tcpServer->SendData(
client, framed.c_str(), static_cast<int>(framed.size()));
return sent ? 0 : -2;
}
void RodWeldSeamTCPProtocol::OnTCPEvent(
const TCPClient* client, TCPServerEventType eventType)
{
bool notify = false;
bool connected = false;
std::size_t count = 0;
{
std::lock_guard<std::mutex> clientLock(m_clientMutex);
const std::size_t previousCount = m_clients.size();
switch (eventType) {
case TCP_EVENT_CLIENT_CONNECTED:
if (client) {
m_clients.insert(client);
m_clientBuffers.emplace(client, QByteArray());
}
break;
case TCP_EVENT_CLIENT_DISCONNECTED:
case TCP_EVENT_CLIENT_EXCEPTION:
m_clients.erase(client);
m_clientBuffers.erase(client);
break;
default:
break;
}
count = m_clients.size();
m_clientCount.store(count);
notify = (previousCount == 0 && count > 0) ||
(previousCount > 0 && count == 0);
connected = count > 0;
}
switch (eventType) {
case TCP_EVENT_CLIENT_CONNECTED:
LOG_INFO("RodWeldSeam TCP client connected: %p, clients=%zu\n", client, count);
break;
case TCP_EVENT_CLIENT_DISCONNECTED:
LOG_INFO("RodWeldSeam TCP client disconnected: %p, clients=%zu\n", client, count);
break;
case TCP_EVENT_CLIENT_EXCEPTION:
LOG_WARNING("RodWeldSeam TCP client exception: %p, clients=%zu\n", client, count);
break;
default:
break;
}
// CYTCPServer 在异常路径会先发 EXCEPTION、随后再发 DISCONNECTED。使用集合和
// 0<->非0 状态转换可确保聚合连接状态只通知一次。
if (notify) {
NotifyConnectionChanged(connected);
}
}
void RodWeldSeamTCPProtocol::OnTCPDataReceived(
const TCPClient* client, const char* data, unsigned int length)
{
if (!client || !data || length == 0 || !m_serverRunning.load()) {
return;
}
std::vector<QByteArray> lines;
bool bufferOverflow = false;
{
std::lock_guard<std::mutex> clientLock(m_clientMutex);
// 只接受已登记的连接,避免断开事件与收包回调交错时重新创建缓存。
if (m_clients.find(client) == m_clients.end()) {
return;
}
QByteArray& buffer = m_clientBuffers[client];
if (length > kMaxClientBufferBytes ||
static_cast<std::size_t>(buffer.size()) + length > kMaxClientBufferBytes) {
buffer.clear();
bufferOverflow = true;
} else {
buffer.append(data, static_cast<int>(length));
while (true) {
int terminatorIndex = buffer.indexOf("\r\n");
int terminatorLength = 2;
// 与参考项目一致,兼容只发送 LF 的客户端;正式协议仍规定 CRLF。
if (terminatorIndex < 0) {
terminatorIndex = buffer.indexOf('\n');
terminatorLength = 1;
}
if (terminatorIndex < 0) {
break;
}
lines.push_back(buffer.left(terminatorIndex).trimmed());
buffer.remove(0, terminatorIndex + terminatorLength);
}
}
}
if (bufferOverflow) {
LOG_WARNING("RodWeldSeam TCP client buffer exceeded %zu bytes: %p\n",
kMaxClientBufferBytes, client);
SendTextToClient(client, "ERR");
return;
}
for (const QByteArray& line : lines) {
if (line.isEmpty()) {
LOG_WARNING("RodWeldSeam TCP received an empty command\n");
SendTextToClient(client, "ERR");
continue;
}
ParseTextCommand(client, line);
}
}
void RodWeldSeamTCPProtocol::ParseTextCommand(
const TCPClient* client, const QByteArray& line)
{
const QString commandLine = QString::fromUtf8(line);
const QStringList tokens = commandLine.split('_', QString::KeepEmptyParts);
// 严格格式W{camera}_X_Y_Z_A_B_C共 7 段且不允许空字段。
if (tokens.size() != 7) {
LOG_WARNING("Invalid RodWeldSeam TCP command segment count: %d\n", tokens.size());
SendTextToClient(client, "ERR");
return;
}
for (const QString& token : tokens) {
if (token.trimmed().isEmpty()) {
LOG_WARNING("Invalid RodWeldSeam TCP command: empty segment\n");
SendTextToClient(client, "ERR");
return;
}
}
const QString typeToken = tokens.at(0).trimmed();
if (typeToken.size() < 2 || typeToken.at(0).toUpper() != QLatin1Char('W')) {
LOG_WARNING("Invalid RodWeldSeam TCP command prefix\n");
SendTextToClient(client, "ERR");
return;
}
const QString cameraToken = typeToken.mid(1);
for (const QChar ch : cameraToken) {
if (!ch.isDigit()) {
LOG_WARNING("Invalid RodWeldSeam TCP camera token\n");
SendTextToClient(client, "ERR");
return;
}
}
bool cameraOk = false;
const int cameraIndex = cameraToken.toInt(&cameraOk);
if (!cameraOk || cameraIndex < 1) {
LOG_WARNING("Invalid RodWeldSeam TCP camera index\n");
SendTextToClient(client, "ERR");
return;
}
RobotPose6D robotPose;
double* values[6] = {
&robotPose.x, &robotPose.y, &robotPose.z,
&robotPose.a, &robotPose.b, &robotPose.c
};
for (int i = 0; i < 6; ++i) {
bool valueOk = false;
const double value = tokens.at(i + 1).trimmed().toDouble(&valueOk);
if (!valueOk || !std::isfinite(value)) {
LOG_WARNING("Invalid RodWeldSeam TCP pose value at segment %d\n", i + 1);
SendTextToClient(client, "ERR");
return;
}
*values[i] = value;
}
DetectionTriggerCallback triggerCallback;
{
std::lock_guard<std::mutex> callbackLock(m_callbackMutex);
triggerCallback = m_detectionTriggerCallback;
}
bool accepted = false;
if (triggerCallback) {
try {
accepted = triggerCallback(cameraIndex, robotPose);
} catch (const std::exception& exception) {
LOG_ERROR("RodWeldSeam TCP trigger callback exception: %s\n", exception.what());
} catch (...) {
LOG_ERROR("RodWeldSeam TCP trigger callback unknown exception\n");
}
}
if (!accepted) {
SendTextToClient(client, "BUSY");
return;
}
LOG_INFO("RodWeldSeam TCP trigger accepted: camera=%d, pose=(%.3f, %.3f, %.3f, %.3f, %.3f, %.3f)\n",
cameraIndex,
robotPose.x, robotPose.y, robotPose.z,
robotPose.a, robotPose.b, robotPose.c);
}
void RodWeldSeamTCPProtocol::NotifyConnectionChanged(bool connected)
{
ConnectionCallback connectionCallback;
{
std::lock_guard<std::mutex> callbackLock(m_callbackMutex);
connectionCallback = m_connectionCallback;
}
if (!connectionCallback) {
return;
}
try {
connectionCallback(connected);
} catch (const std::exception& exception) {
LOG_ERROR("RodWeldSeam TCP connection callback exception: %s\n", exception.what());
} catch (...) {
LOG_ERROR("RodWeldSeam TCP connection callback unknown exception\n");
}
}