413 lines
12 KiB
C++
413 lines
12 KiB
C++
|
|
#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");
|
|||
|
|
}
|
|||
|
|
}
|