GrabBag/App/WeldSeam/WeldSeamApp/Presenter/Src/WeldSeamTCPProtocol.cpp

188 lines
6.1 KiB
C++

#include "WeldSeamTCPProtocol.h"
#include "IYTCPServer.h"
#include "VrLog.h"
#include <QByteArray>
#include <QMetaObject>
#include <QThread>
#include <atomic>
#include <map>
#include <mutex>
#include <set>
#include <vector>
namespace {
bool ParseIndexedCommand(const QString& command, const QString& name, int& cameraIndex)
{
const QString prefix = name + QLatin1Char('_');
if (!command.startsWith(prefix)) {
return false;
}
bool valid = false;
cameraIndex = command.mid(prefix.size()).toInt(&valid);
return valid && cameraIndex > 0;
}
} // namespace
class WeldSeamTCPProtocol::Impl
{
public:
explicit Impl(WeldSeamTCPProtocol* owner) : q(owner) {}
bool Start(uint16_t port)
{
if (!VrCreatYTCPServer(&server) || !server) return false;
if (!server->Init(port)) {
delete server;
server = nullptr;
return false;
}
server->SetEventCallback([this](const TCPClient* client, TCPServerEventType type) {
OnEvent(client, type);
});
if (!server->Start([this](const TCPClient* client, const char* data, unsigned int size) {
OnData(client, data, size);
})) {
server->Close();
delete server;
server = nullptr;
return false;
}
running = true;
LOG_INFO("WeldSeam TCP server listening on %u\n", port);
return true;
}
void Stop()
{
running = false;
connected = false;
if (server) {
server->Stop();
server->Close();
delete server;
server = nullptr;
}
std::lock_guard<std::mutex> lock(clientMutex);
clients.clear();
buffers.clear();
}
void Send(const QString& message)
{
if (!running || !server) return;
QByteArray packet = message.toUtf8();
if (!packet.endsWith("\r\n")) packet.append("\r\n");
server->SendAllData(packet.constData(), static_cast<unsigned int>(packet.size()));
}
private:
void OnEvent(const TCPClient* client, TCPServerEventType type)
{
bool notify = false;
bool isConnected = connected.load();
if (type == TCP_EVENT_CLIENT_CONNECTED) {
std::lock_guard<std::mutex> lock(clientMutex);
clients.insert(client);
const bool wasConnected = connected.exchange(true);
isConnected = true;
notify = !wasConnected;
} else if (type == TCP_EVENT_CLIENT_DISCONNECTED || type == TCP_EVENT_CLIENT_EXCEPTION) {
std::lock_guard<std::mutex> lock(clientMutex);
buffers.erase(client);
clients.erase(client);
isConnected = !clients.empty();
const bool wasConnected = connected.exchange(isConnected);
notify = wasConnected != isConnected;
}
if (notify) {
QMetaObject::invokeMethod(q, [this, isConnected]() {
emit q->ConnectionChanged(isConnected);
}, Qt::QueuedConnection);
}
}
void OnData(const TCPClient* client, const char* data, unsigned int size)
{
if (!data || size == 0) return;
std::vector<QString> commands;
{
std::lock_guard<std::mutex> lock(clientMutex);
QByteArray& buffer = buffers[client];
buffer.append(data, static_cast<int>(size));
while (true) {
const int end = buffer.indexOf("\r\n");
if (end < 0) break;
const QString command = QString::fromUtf8(buffer.left(end)).trimmed();
buffer.remove(0, end + 2);
if (!command.isEmpty()) commands.push_back(command);
}
}
for (const auto& command : commands) Process(command);
}
void Process(const QString& command)
{
const QString normalized = command.trimmed().toUpper();
QMetaObject::invokeMethod(q, [this, command]() { emit q->DataReceived(command); },
Qt::QueuedConnection);
int cameraIndex = 0;
if (ParseIndexedCommand(normalized, QStringLiteral("START_SCAN"), cameraIndex)) {
QMetaObject::invokeMethod(q, [this, cameraIndex]()
{
emit q->StartScanRequested(cameraIndex);
}, Qt::QueuedConnection);
} else if (normalized == QStringLiteral("STOP_SCAN")) {
QMetaObject::invokeMethod(q, [this]() { emit q->StopScanRequested(); },
Qt::QueuedConnection);
} else if (ParseIndexedCommand(normalized, QStringLiteral("GET_RET"), cameraIndex)) {
QMetaObject::invokeMethod(q, [this, cameraIndex]()
{
emit q->GetResultRequested(cameraIndex);
}, Qt::QueuedConnection);
} else if (normalized.startsWith(QStringLiteral("START_SCAN")) ||
normalized.startsWith(QStringLiteral("STOP_SCAN")) ||
normalized.startsWith(QStringLiteral("GET_RET"))) {
q->SendMessage(QStringLiteral("ERROR_INVALID_FORMAT"));
} else {
q->SendMessage(QStringLiteral("ERROR_UNKNOWN_COMMAND"));
}
}
public:
WeldSeamTCPProtocol* q = nullptr;
IYTCPServer* server = nullptr;
std::atomic<bool> running{false};
std::atomic<bool> connected{false};
std::set<const TCPClient*> clients;
std::map<const TCPClient*, QByteArray> buffers;
std::mutex clientMutex;
};
WeldSeamTCPProtocol::WeldSeamTCPProtocol(QObject* parent)
: QObject(parent), m_impl(std::make_unique<Impl>(this))
{
}
WeldSeamTCPProtocol::~WeldSeamTCPProtocol()
{
StopServer();
}
bool WeldSeamTCPProtocol::StartServer(uint16_t port) { return m_impl->Start(port); }
void WeldSeamTCPProtocol::StopServer() { m_impl->Stop(); }
bool WeldSeamTCPProtocol::IsConnected() const { return m_impl->connected.load(); }
void WeldSeamTCPProtocol::SendMessage(const QString& message)
{
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, [this, message]() { m_impl->Send(message); },
Qt::QueuedConnection);
return;
}
m_impl->Send(message);
}