GrabBag/App/ParkingSpaceGuide/ParkingSpaceGuideApp/Presenter/Src/ParkingSpaceGuideRemoteServer.cpp

368 lines
13 KiB
C++
Raw Normal View History

2026-07-30 12:06:02 +08:00
#include "ParkingSpaceGuideRemoteServer.h"
#include <utility>
#include <QDateTime>
#include <QHostAddress>
#include <QHostInfo>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QSysInfo>
#include <QUdpSocket>
#include "IVrZeroMQPubSub.h"
#include "IVrZeroMQServer.h"
#include "VrLog.h"
namespace {
const char kRemoteProtocol[] = "ParkingSpaceGuideRemote";
const char kRemoteProtocolVersion[] = "1.0";
const char kRemoteService[] = "ParkingSpaceGuide";
QJsonObject ErrorResponse(const QString& command,
int code,
const QString& message)
{
QJsonObject response;
response["ok"] = false;
response["code"] = code;
response["cmd"] = command;
response["message"] = message;
response["protocol"] = QString::fromLatin1(kRemoteProtocol);
response["version"] = QString::fromLatin1(kRemoteProtocolVersion);
return response;
}
} // namespace
ParkingSpaceGuideRemoteServer::ParkingSpaceGuideRemoteServer(QObject* parent)
: QObject(parent)
{
}
ParkingSpaceGuideRemoteServer::~ParkingSpaceGuideRemoteServer()
{
Stop();
}
bool ParkingSpaceGuideRemoteServer::Start(
const RemoteViewConfig& config,
ModelHandler modelHandler,
StartHandler startHandler,
StopHandler stopHandler,
StatusProvider statusProvider,
RunningProvider runningProvider,
LogHandler logHandler)
{
Stop();
m_config = config;
m_modelHandler = std::move(modelHandler);
m_startHandler = std::move(startHandler);
m_stopHandler = std::move(stopHandler);
m_statusProvider = std::move(statusProvider);
m_runningProvider = std::move(runningProvider);
m_logHandler = std::move(logHandler);
SetError(QString());
if (!m_config.enabled) {
return true;
}
if (!IVrZeroMQPublisher::CreateObject(&m_statusPublisher) ||
!m_statusPublisher ||
m_statusPublisher->Init(m_config.publishPort) != 0) {
SetError(QStringLiteral("初始化远程状态发布端口失败:%1")
.arg(m_config.publishPort));
Stop();
return false;
}
if (!IVrZeroMQServer::CreateObject(&m_controlServer) || !m_controlServer) {
SetError(QStringLiteral("创建远程控制服务失败"));
Stop();
return false;
}
const auto receiveHandler = [this](const char* data, size_t length) {
const QByteArray request(data, static_cast<int>(length));
const QByteArray response = HandleControlMessage(request);
return std::string(response.constData(),
static_cast<size_t>(response.size()));
};
if (m_controlServer->Init(m_config.controlPort,
FunMQEvent(),
receiveHandler,
false) != 0) {
SetError(QStringLiteral("初始化远程控制端口失败:%1")
.arg(m_config.controlPort));
Stop();
return false;
}
m_discoverySocket = new QUdpSocket(this);
if (!m_discoverySocket->bind(
QHostAddress::AnyIPv4,
static_cast<quint16>(m_config.discoveryPort),
QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint)) {
SetError(QStringLiteral("初始化UDP发现端口失败%1%2")
.arg(m_config.discoveryPort)
.arg(m_discoverySocket->errorString()));
Stop();
return false;
}
connect(m_discoverySocket, &QUdpSocket::readyRead, this, [this]() {
HandleDiscoveryDatagrams();
});
m_running.store(true);
LOG_INFO("ParkingSpaceGuide remote service discovery=%d control=%d publish=%d topic=%s\n",
m_config.discoveryPort,
m_config.controlPort,
m_config.publishPort,
m_config.topic.c_str());
return true;
}
void ParkingSpaceGuideRemoteServer::Stop()
{
m_running.store(false);
if (m_discoverySocket) {
m_discoverySocket->close();
delete m_discoverySocket;
m_discoverySocket = nullptr;
}
if (m_controlServer) {
delete m_controlServer;
m_controlServer = nullptr;
}
{
std::lock_guard<std::mutex> lock(m_publishMutex);
if (m_statusPublisher) {
m_statusPublisher->UnInit();
delete m_statusPublisher;
m_statusPublisher = nullptr;
}
}
}
bool ParkingSpaceGuideRemoteServer::IsRunning() const
{
return m_running.load();
}
bool ParkingSpaceGuideRemoteServer::PublishStatus(
const std::string& statusJson)
{
std::lock_guard<std::mutex> lock(m_publishMutex);
if (!m_running.load() || !m_statusPublisher || statusJson.empty()) {
return false;
}
const int result = m_statusPublisher->Publish(
m_config.topic.c_str(),
statusJson.data(),
statusJson.size());
if (result != 0) {
SetError(QStringLiteral("远程状态发布失败:%1").arg(result));
return false;
}
return true;
}
QString ParkingSpaceGuideRemoteServer::LastError() const
{
std::lock_guard<std::mutex> lock(m_errorMutex);
return m_lastError;
}
QJsonObject ParkingSpaceGuideRemoteServer::BuildServerInfo(
const QString& command) const
{
QJsonObject response;
response["ok"] = true;
response["code"] = 0;
response["cmd"] = command;
response["protocol"] = QString::fromLatin1(kRemoteProtocol);
response["version"] = QString::fromLatin1(kRemoteProtocolVersion);
response["service"] = QString::fromLatin1(kRemoteService);
response["deviceName"] = QStringLiteral("ParkingSpaceGuideApp");
response["deviceId"] = QString::fromStdString(DeviceId());
response["controlPort"] = m_config.controlPort;
response["publishPort"] = m_config.publishPort;
response["topic"] = QString::fromStdString(m_config.topic);
response["guidanceRunning"] = m_runningProvider
? m_runningProvider()
: false;
response["timestamp"] = QDateTime::currentMSecsSinceEpoch();
return response;
}
QByteArray ParkingSpaceGuideRemoteServer::HandleControlMessage(
const QByteArray& request)
{
QJsonParseError parseError;
const QJsonDocument document = QJsonDocument::fromJson(request, &parseError);
if (parseError.error != QJsonParseError::NoError || !document.isObject()) {
return QJsonDocument(ErrorResponse(QString(), -1,
QStringLiteral("控制请求不是有效JSON")))
.toJson(QJsonDocument::Compact);
}
const QJsonObject object = document.object();
const QString command = object.value("cmd").toString().trimmed();
if (object.value("protocol").toString() !=
QString::fromLatin1(kRemoteProtocol)) {
return QJsonDocument(ErrorResponse(command, -5,
QStringLiteral("远程控制协议不匹配")))
.toJson(QJsonDocument::Compact);
}
QJsonObject response = BuildServerInfo(command);
if (command == QStringLiteral("select_model")) {
const QString modelType = object.value("modelType")
.toString()
.trimmed()
.toUpper();
if (modelType != QStringLiteral("A320") &&
modelType != QStringLiteral("B737")) {
response = ErrorResponse(command, -2,
QStringLiteral("modelType必须为A320或B737"));
} else if (!m_modelHandler || !m_modelHandler(modelType)) {
response = ErrorResponse(command, -3,
QStringLiteral("App未接受所选机型"));
} else {
response["modelType"] = modelType;
response["message"] = QStringLiteral("机型已同步到App");
}
} else if (command == QStringLiteral("start")) {
const QString modelType = object.value("modelType")
.toString()
.trimmed()
.toUpper();
if (!modelType.isEmpty() &&
modelType != QStringLiteral("A320") &&
modelType != QStringLiteral("B737")) {
response = ErrorResponse(command, -2,
QStringLiteral("modelType必须为空、A320或B737"));
} else if (!m_startHandler || !m_startHandler(modelType)) {
response = ErrorResponse(command, -3,
QStringLiteral("服务端拒绝启动停机引导,请检查设备或安全状态"));
} else {
response["modelType"] = modelType;
response["guidanceRunning"] = true;
}
} else if (command == QStringLiteral("stop")) {
const int result = m_stopHandler ? m_stopHandler() : -1;
if (result != 0) {
response = ErrorResponse(command, result,
QStringLiteral("停止停机引导失败"));
} else {
response["guidanceRunning"] = false;
}
} else if (command == QStringLiteral("get_status")) {
const std::string status = m_statusProvider
? m_statusProvider()
: std::string();
QJsonParseError statusError;
const QJsonDocument statusDocument = QJsonDocument::fromJson(
QByteArray(status.data(), static_cast<int>(status.size())),
&statusError);
if (statusError.error == QJsonParseError::NoError &&
statusDocument.isObject()) {
response["status"] = statusDocument.object();
} else {
response["status"] = QJsonObject();
}
} else if (command != QStringLiteral("get_info") &&
command != QStringLiteral("ping")) {
response = ErrorResponse(command, -4,
QStringLiteral("不支持的控制命令"));
}
return QJsonDocument(response).toJson(QJsonDocument::Compact);
}
void ParkingSpaceGuideRemoteServer::HandleDiscoveryDatagrams()
{
while (m_discoverySocket && m_discoverySocket->hasPendingDatagrams()) {
QByteArray request;
request.resize(static_cast<int>(
m_discoverySocket->pendingDatagramSize()));
QHostAddress sender;
quint16 senderPort = 0;
const qint64 bytes = m_discoverySocket->readDatagram(
request.data(), request.size(), &sender, &senderPort);
if (bytes <= 0) {
continue;
}
request.resize(static_cast<int>(bytes));
QJsonParseError parseError;
const QJsonDocument document = QJsonDocument::fromJson(
request, &parseError);
if (parseError.error != QJsonParseError::NoError ||
!document.isObject()) {
continue;
}
const QJsonObject object = document.object();
if (object.value("cmd").toString() != QStringLiteral("discover")) {
continue;
}
if (object.value("protocol").toString() !=
QString::fromLatin1(kRemoteProtocol)) {
continue;
}
const QString service = object.value("service").toString();
if (!service.isEmpty() &&
service != QString::fromLatin1(kRemoteService)) {
continue;
}
const QByteArray response = QJsonDocument(
BuildServerInfo(QStringLiteral("discover")))
.toJson(QJsonDocument::Compact);
const qint64 sentBytes = m_discoverySocket->writeDatagram(
response, sender, senderPort);
const QByteArray senderText = sender.toString().toUtf8();
if (sentBytes < 0) {
const QByteArray errorText = m_discoverySocket->errorString()
.toUtf8();
LOG_ERROR("ParkingSpaceGuide discovery reply failed peer=%s:%u error=%s\n",
senderText.constData(),
static_cast<unsigned int>(senderPort),
errorText.constData());
} else {
LOG_INFO("ParkingSpaceGuide discovery request peer=%s:%u replyBytes=%lld\n",
senderText.constData(),
static_cast<unsigned int>(senderPort),
static_cast<long long>(sentBytes));
}
if (m_logHandler) {
m_logHandler(sentBytes < 0
? QStringLiteral("远程显示搜索回复失败:%1:%2%3")
.arg(sender.toString())
.arg(senderPort)
.arg(m_discoverySocket->errorString())
: QStringLiteral("收到远程显示搜索:%1:%2已回复")
.arg(sender.toString())
.arg(senderPort));
}
}
}
std::string ParkingSpaceGuideRemoteServer::DeviceId() const
{
const QByteArray uniqueId = QSysInfo::machineUniqueId();
if (!uniqueId.isEmpty()) {
return uniqueId.toHex().toStdString();
}
return QHostInfo::localHostName().toStdString();
}
void ParkingSpaceGuideRemoteServer::SetError(const QString& message)
{
std::lock_guard<std::mutex> lock(m_errorMutex);
m_lastError = message;
}