#include "ParkingSpaceGuideRemoteServer.h" #include #include #include #include #include #include #include #include #include #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(length)); const QByteArray response = HandleControlMessage(request); return std::string(response.constData(), static_cast(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(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 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 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 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(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( 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(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(senderPort), errorText.constData()); } else { LOG_INFO("ParkingSpaceGuide discovery request peer=%s:%u replyBytes=%lld\n", senderText.constData(), static_cast(senderPort), static_cast(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 lock(m_errorMutex); m_lastError = message; }