#include "ParkingSpaceGuideRemoteServer.h" #include #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.1"; const char kRemoteService[] = "ParkingSpaceGuide"; bool CommandRequiresPassword(const QString& command) { return command == QStringLiteral("login") || command == QStringLiteral("select_model") || command == QStringLiteral("start") || command == QStringLiteral("stop") || command == QStringLiteral("set_maintenance") || command == QStringLiteral("chock_on") || command == QStringLiteral("chock_off") || command == QStringLiteral("list_sessions") || command == QStringLiteral("get_session_samples"); } 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, ControlHandlers handlers) { Stop(); m_config = config; m_handlers = std::move(handlers); 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) { LOG_INFO("[ParkingSpaceGuide][Shutdown] Remote discovery close begin\n"); m_discoverySocket->close(); delete m_discoverySocket; m_discoverySocket = nullptr; LOG_INFO("[ParkingSpaceGuide][Shutdown] Remote discovery close complete\n"); } if (m_controlServer) { LOG_INFO("[ParkingSpaceGuide][Shutdown] Remote control UnInit begin\n"); m_controlServer->UnInit(); delete m_controlServer; m_controlServer = nullptr; LOG_INFO("[ParkingSpaceGuide][Shutdown] Remote control UnInit complete\n"); } { std::lock_guard lock(m_publishMutex); if (m_statusPublisher) { LOG_INFO("[ParkingSpaceGuide][Shutdown] Remote publish UnInit begin\n"); m_statusPublisher->UnInit(); delete m_statusPublisher; m_statusPublisher = nullptr; LOG_INFO("[ParkingSpaceGuide][Shutdown] Remote publish UnInit complete\n"); } } } 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["activated"] = m_handlers.activatedProvider ? m_handlers.activatedProvider() : false; response["workMode"] = m_handlers.workModeProvider ? m_handlers.workModeProvider() : QStringLiteral("normal"); response["guidanceRunning"] = m_handlers.runningProvider ? m_handlers.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); } if (CommandRequiresPassword(command)) { const QString password = object.value("password").toString(); if (!m_handlers.passwordHandler || !m_handlers.passwordHandler(password)) { return QJsonDocument(ErrorResponse(command, -6, QStringLiteral("密码错误或未登录"))) .toJson(QJsonDocument::Compact); } } QJsonObject response = BuildServerInfo(command); if (command == QStringLiteral("login")) { response["message"] = QStringLiteral("登录成功"); } else if (command == QStringLiteral("select_model")) { const QString modelType = object.value("modelType") .toString() .trimmed() .toUpper(); if (modelType.isEmpty() || (m_handlers.modelValidator && !m_handlers.modelValidator(modelType))) { response = ErrorResponse(command, -2, QStringLiteral("机型不在配置表中")); } else if (!m_handlers.modelHandler || !m_handlers.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() && m_handlers.modelValidator && !m_handlers.modelValidator(modelType)) { response = ErrorResponse(command, -2, QStringLiteral("机型不在配置表中")); } else if (!m_handlers.startHandler || !m_handlers.startHandler(modelType)) { response = ErrorResponse(command, -3, QStringLiteral("服务端拒绝启动停机引导,请检查设备或安全状态")); } else { response["modelType"] = modelType; response["guidanceRunning"] = true; } } else if (command == QStringLiteral("stop")) { const int result = m_handlers.stopHandler ? m_handlers.stopHandler() : -1; if (result != 0) { response = ErrorResponse(command, result, QStringLiteral("停止停机引导失败")); } else { response["guidanceRunning"] = false; } } else if (command == QStringLiteral("set_maintenance")) { const bool enabled = object.value("enabled").toBool(); if (!m_handlers.maintenanceHandler || !m_handlers.maintenanceHandler(enabled)) { response = ErrorResponse(command, -3, QStringLiteral("切换维护模式失败")); } else { response["maintenanceMode"] = enabled; response["workMode"] = enabled ? QStringLiteral("maintenance") : QStringLiteral("normal"); response["message"] = enabled ? QStringLiteral("已进入维护模式") : QStringLiteral("已退出维护模式"); } } else if (command == QStringLiteral("chock_on") || command == QStringLiteral("chock_off")) { const bool chockOn = command == QStringLiteral("chock_on"); if (!m_handlers.chockHandler || !m_handlers.chockHandler(chockOn)) { response = ErrorResponse(command, -3, QStringLiteral("记录轮挡时间失败")); } else { response["message"] = chockOn ? QStringLiteral("已记录上轮挡时间") : QStringLiteral("已记录下轮挡时间"); } } else if (command == QStringLiteral("get_panel_config")) { response["panel"] = m_handlers.panelConfigProvider ? m_handlers.panelConfigProvider() : QJsonObject(); } else if (command == QStringLiteral("list_sessions")) { const int limit = object.value("limit").toInt(50); response["sessions"] = m_handlers.sessionsProvider ? m_handlers.sessionsProvider(limit) : QJsonArray(); } else if (command == QStringLiteral("get_session_samples")) { const QString sessionUuid = object.value("sessionUuid") .toString() .trimmed(); if (sessionUuid.isEmpty()) { response = ErrorResponse(command, -2, QStringLiteral("sessionUuid不能为空")); } else { response["sessionUuid"] = sessionUuid; response["samples"] = m_handlers.samplesProvider ? m_handlers.samplesProvider(sessionUuid) : QJsonArray(); } } else if (command == QStringLiteral("get_status")) { const std::string status = m_handlers.statusProvider ? m_handlers.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("不支持的控制命令")); } if (response.value("ok").toBool()) { if (m_handlers.activatedProvider) { response["activated"] = m_handlers.activatedProvider(); } if (m_handlers.workModeProvider) { response["workMode"] = m_handlers.workModeProvider(); } } 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_handlers.logHandler) { m_handlers.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; }