#include "ParkingSpaceGuidePresenter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "DetectPresenter.h" #include "IYUDPClient.h" #include "LaserDataLoader.h" #include "ParkingStatusHttpServer.h" #include "PathManager.h" #include "Version.h" #include "VrLog.h" namespace { constexpr int kMvsPixelTypeMono8 = 0x01080001; constexpr int kMvsPixelTypeRGB8 = 0x02180014; constexpr int kMvsPixelTypeBGR8 = 0x02180015; constexpr int kMvsAcqModeContinuous = 2; constexpr int kAirplaneProbeIntervalMs = 1000; constexpr unsigned int kModelCaptureTimeoutMs = 1000; void SortPathsNaturally(std::vector& paths) { QCollator collator; collator.setCaseSensitivity(Qt::CaseInsensitive); collator.setNumericMode(true); std::sort(paths.begin(), paths.end(), [&collator](const QString& lhs, const QString& rhs) { return collator.compare(QFileInfo(lhs).fileName(), QFileInfo(rhs).fileName()) < 0; }); } QString ConfigText(const std::string& value, const QString& fallback) { const QString text = QString::fromStdString(value).trimmed(); return text.isEmpty() ? fallback : text; } QString ResultText(const QString& value, const QString& fallback) { const QString text = value.trimmed(); return text.isEmpty() ? fallback : text; } ParkingSpaceGuideInfo FallbackGuideInfo(const UdpBroadcastConfig& config) { ParkingSpaceGuideInfo info; info.targetId = ConfigText(config.targetId, QStringLiteral("T001")); info.parkId = ConfigText(config.parkId, QStringLiteral("P01")); info.modelType = QStringLiteral("未知"); return info; } bool IsExceptionGuideState(int guideStateCode) { switch (guideStateCode) { case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 19: case 20: case 21: case 22: case 23: case 24: return true; default: return false; } } QJsonObject BuildGuideInfoJson(const ParkingSpaceGuideInfo& info, const UdpBroadcastConfig& config) { const ParkingSpaceGuideInfo fallback = FallbackGuideInfo(config); QJsonObject object; object["targetId"] = ResultText(info.targetId, fallback.targetId); object["parkId"] = ResultText(info.parkId, fallback.parkId); object["modelType"] = ResultText(info.modelType, fallback.modelType); object["guideStateCode"] = info.guideStateCode; object["distanceToStop"] = info.distance; object["bodyYawAngle"] = info.angle; object["lateralOffset"] = info.lateralOffset; object["aircraftSpeed"] = info.aircraftSpeed; object["hasException"] = info.hasException || IsExceptionGuideState(info.guideStateCode); object["guideText"] = info.guideText; return object; } QJsonObject BuildDetectionResultPayload(const DetectionResult& result, const UdpBroadcastConfig& config) { const bool hasResult = !result.parkingSpaceInfoList.empty(); const bool success = result.errorCode == 0 && hasResult; const ParkingSpaceGuideInfo firstInfo = hasResult ? result.parkingSpaceInfoList.front() : FallbackGuideInfo(config); const QJsonObject firstResult = BuildGuideInfoJson(firstInfo, config); QJsonObject payload; payload["protocol"] = QStringLiteral("ParkingSpaceGuideResult"); payload["version"] = QStringLiteral("1.0"); payload["success"] = success; payload["hasResult"] = hasResult; payload["targetId"] = firstResult["targetId"]; payload["parkId"] = firstResult["parkId"]; payload["modelType"] = firstResult["modelType"]; payload["guideStateCode"] = firstResult["guideStateCode"]; payload["distanceToStop"] = firstResult["distanceToStop"]; payload["bodyYawAngle"] = firstResult["bodyYawAngle"]; payload["lateralOffset"] = firstResult["lateralOffset"]; payload["aircraftSpeed"] = firstResult["aircraftSpeed"]; payload["hasException"] = result.errorCode != 0 || firstResult["hasException"].toBool(); payload["guideText"] = firstResult["guideText"]; payload["message"] = result.message; payload["errorCode"] = result.errorCode; payload["timestamp"] = QDateTime::currentMSecsSinceEpoch(); return payload; } } // namespace ParkingSpaceGuidePresenter::ParkingSpaceGuidePresenter(QObject* parent) : ParkingSpaceGuidePresenter(ParkingSpaceGuideSimulationOptions(), parent) { } qint64 MonotonicMilliseconds() { return std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch()) .count(); } ParkingSpaceGuidePresenter::ParkingSpaceGuidePresenter( const ParkingSpaceGuideSimulationOptions& simulationOptions, QObject* parent) : QObject(parent) , m_simulationOptions(simulationOptions) { m_simulationOptions.frameIntervalMs = (std::max)(1, m_simulationOptions.frameIntervalMs); } ParkingSpaceGuidePresenter::~ParkingSpaceGuidePresenter() { DeinitApp(); } int ParkingSpaceGuidePresenter::Init() { m_reconfiguring.store(false); NotifyWorkStatus(WorkStatus::InitIng); NotifyStatus("停机引导初始化中"); int ret = InitConfig(); if (ret != 0) { NotifyWorkStatus(WorkStatus::Error); return ret; } const ConfigResult config = m_configManager->GetConfigResult(); m_cameraList.clear(); DetectionResult initialStatus; initialStatus.errorCode = -1; initialStatus.message = QStringLiteral("暂无检测结果"); UpdateCurrentStatus(initialStatus); DetectionResult waitingDisplay; ParkingSpaceGuideInfo waitingInfo; waitingInfo.targetId = ConfigText( config.udpBroadcastConfig.targetId, QStringLiteral("T001")); waitingInfo.parkId = ConfigText( config.udpBroadcastConfig.parkId, QStringLiteral("P01")); waitingInfo.modelType = QStringLiteral("未知"); waitingInfo.guideStateCode = static_cast(ParkingGuideState::Waiting); waitingInfo.guideText = QStringLiteral("WAIT"); waitingDisplay.parkingSpaceInfoList.push_back(waitingInfo); waitingDisplay.message = QStringLiteral("等待飞机进入引导区域"); m_detectPresenter = new DetectPresenter(); if (m_simulationOptions.enabled) { ret = InitSimulationData(); if (ret != 0) { NotifyWorkStatus(WorkStatus::Error); return ret; } } else { ret = InitMvsCamera(config.mvsCamera); if (ret != 0) { NotifyStatus("平面相机初始化失败,继续使用预留算法模式"); } if (m_cameraList.empty()) { const std::string cameraName = config.mvsCamera.serialNumber.empty() ? std::string("平面相机") : config.mvsCamera.serialNumber; m_cameraList.push_back(std::make_pair(cameraName, static_cast(m_mvsDevice))); } ret = InitRsLidar(config.lidarConfig); if (ret != 0) { NotifyStatus("雷达初始化失败,继续使用预留算法模式"); } } ret = InitLedDisplay(config.ledDisplayConfig); if (ret != 0) { NotifyStatus("显示屏初始化失败"); } ret = InitUdpBroadcast(config.udpBroadcastConfig); if (ret != 0) { NotifyStatus("组播初始化失败"); } ret = InitHttpServer(config.httpServerConfig); if (ret != 0) { NotifyStatus("HTTP状态服务初始化失败"); } if (StatusCallback()) { StatusCallback()->OnCameraCountChanged(1); } if (StatusCallback()) { StatusCallback()->OnDetectionResult(waitingDisplay); } m_initialized = true; NotifyWorkStatus(WorkStatus::Ready); NotifyStatus("停机引导初始化完成"); return 0; } void ParkingSpaceGuidePresenter::DeinitApp() { m_initialized.store(false); { std::lock_guard reconfigurationLock(m_reconfigurationMutex); m_reconfiguring.store(true); StopDetection(); CloseDevices(); delete m_detectPresenter; m_detectPresenter = nullptr; } if (m_configManager) { m_configManager->Shutdown(); delete m_configManager; m_configManager = nullptr; } } bool ParkingSpaceGuidePresenter::StartDetection(int cameraIndex) { std::lock_guard threadLock(m_detectionThreadMutex); if (!m_initialized || m_reconfiguring.load() || !m_detectPresenter || !m_configManager) { NotifyStatus("系统未初始化"); return false; } if (m_simulationOptions.enabled && (m_simulationRadarFrames.size() < 2 || m_simulationCameraFrames.empty())) { NotifyStatus("多帧仿真点云或图像数据未就绪"); return false; } if (!m_simulationOptions.enabled && (!m_lidarDevice || !m_lidarDevice->IsRunning())) { NotifyStatus("雷达未连接,无法启动实时检测"); return false; } if (m_detectionRunning.load()) { NotifyStatus(m_simulationOptions.enabled ? "停机引导仿真回放已在运行" : "停机引导实时检测已在运行"); return true; } if (m_simulationThread.joinable()) { m_simulationThread.join(); } if (m_detectionThread.joinable()) { m_detectionThread.join(); } if (cameraIndex > 0) { m_detectIndex = cameraIndex; } else { m_detectIndex = m_currentCameraIndex; } { std::lock_guard dataLock(m_dataMutex); m_consumedCloudSequence = m_cloudSequence; m_processedCloudSequence = m_cloudSequence; m_lidarErrorPending = false; m_lidarErrorMessage.clear(); } m_trackingMode.store(false); m_simulationRound.store(0); m_lastProbeAcceptedMs.store(0); m_stopDetectionRequested.store(false); m_detectionRunning.store(true); NotifyWorkStatus(WorkStatus::Working); NotifyStatus(m_simulationOptions.enabled ? "停机引导多帧仿真回放已开始" : "停机引导实时检测已开始"); try { m_detectionThread = std::thread(&ParkingSpaceGuidePresenter::DetectionLoop, this); if (m_simulationOptions.enabled) { m_simulationThread = std::thread( &ParkingSpaceGuidePresenter::SimulationPlaybackLoop, this); } } catch (...) { m_stopDetectionRequested.store(true); m_detectionCondition.notify_all(); if (m_simulationThread.joinable()) { m_simulationThread.join(); } if (m_detectionThread.joinable()) { m_detectionThread.join(); } m_detectionRunning.store(false); NotifyWorkStatus(WorkStatus::Error); NotifyStatus(m_simulationOptions.enabled ? "启动停机引导仿真线程失败" : "启动停机引导实时检测线程失败"); return false; } return true; } bool ParkingSpaceGuidePresenter::TriggerDetection(int cameraIndex) { std::lock_guard threadLock(m_detectionThreadMutex); if (!m_initialized || m_reconfiguring.load() || !m_detectPresenter || !m_configManager) { NotifyStatus("系统未初始化"); return false; } if (m_detectionRunning.load()) { NotifyStatus("实时检测运行中,无法执行单次检测"); return false; } if (cameraIndex > 0) { m_detectIndex = cameraIndex; } else { m_detectIndex = m_currentCameraIndex; } std::shared_ptr cloudFrame; { std::lock_guard dataLock(m_dataMutex); cloudFrame = m_latestCloudFrame; } if (!cloudFrame) { NotifyStatus("暂无可用雷达点云"); return false; } NotifyWorkStatus(WorkStatus::Detecting); NotifyStatus("停机引导单次检测已开始"); bool success = false; ParkingGuideAlgorithmState algorithmState; try { success = DetectOnce(cloudFrame->cloud, cloudFrame->timestampMs, algorithmState); } catch (const std::exception& e) { NotifyStatus(std::string("停机引导单次检测异常:") + e.what()); } catch (...) { NotifyStatus("停机引导单次检测发生未知异常"); } NotifyWorkStatus(success ? WorkStatus::Completed : WorkStatus::Error); NotifyStatus(success ? "停机引导单次检测完成" : "停机引导单次检测失败"); return success; } bool ParkingSpaceGuidePresenter::DetectOnce(const RsCloudData& cloud, qint64 frameTimestampMs, ParkingGuideAlgorithmState& algorithmState) { std::lock_guard executionLock(m_detectionExecutionMutex); if (!m_detectPresenter || !m_configManager) { return false; } DetectionResult result; ParkingGuideAlgorithmControl control; result.cameraIndex = m_detectIndex; const int ret = m_detectPresenter->DetectParkingSpaceGuide(cloud, m_configManager->GetAlgorithmParams(), frameTimestampMs, algorithmState, result, control); result.cameraIndex = m_detectIndex; if (ret != 0) { result.errorCode = ret; if (result.message.isEmpty()) { result.message = QStringLiteral("停机引导检测失败"); } } else { if (result.parkingSpaceInfoList.empty() && result.message.isEmpty()) { result.message = QStringLiteral("无停机引导结果"); } if (control.needModelRecognition) { const auto appendRecognitionFailure = [this, &result, &control](const QString& reason) { const QString text = reason.trimmed().isEmpty() ? QStringLiteral("机型识别失败") : reason.trimmed(); NotifyStatus(text.toUtf8().toStdString()); result.message = result.message.trimmed().isEmpty() ? text : result.message + QStringLiteral(";") + text; const int maxAttempts = (std::max)( 1, m_configManager->GetAlgorithmParams() .modelRecognitionParam.maxRecognitionAttempts); if (control.nextState.modelRecognitionAttempts >= maxAttempts) { for (ParkingSpaceGuideInfo& info : result.parkingSpaceInfoList) { info.hasException = true; info.guideStateCode = static_cast( ParkingGuideState::AircraftVerificationFailed); info.guideText = QStringLiteral("STOP+IDFAIL"); } } }; try { QImage modelImage; QString captureError; if (!CaptureModelRecognitionImage(modelImage, captureError)) { appendRecognitionFailure(captureError); } else { result.image = modelImage; ModelRecognitionResult modelResult; const int modelRet = m_detectPresenter->RecognizeModel2D( modelImage, control.recognitionContext, modelResult); const double minimumConfidence = m_configManager->GetAlgorithmParams() .modelRecognitionParam.confidenceThreshold; if (modelRet == 0 && modelResult.IsVerified(minimumConfidence)) { control.nextState.modelType = modelResult.modelType.trimmed(); control.nextState.modelConfidence = modelResult.confidence; control.nextState.modelVerified = true; for (ParkingSpaceGuideInfo& info : result.parkingSpaceInfoList) { info.modelType = control.nextState.modelType; info.confidence = control.nextState.modelConfidence; } } else { QString reason = modelResult.message.trimmed(); if (reason.isEmpty() && modelRet == 0) { reason = QStringLiteral( "机型识别置信度不足:%1,实际%2,阈值%3") .arg(modelResult.modelType) .arg(modelResult.confidence, 0, 'f', 3) .arg(minimumConfidence, 0, 'f', 3); } if (reason.isEmpty()) { reason = QStringLiteral("机型识别结果未通过验证"); } appendRecognitionFailure(reason); } } } catch (const std::exception& e) { appendRecognitionFailure( QStringLiteral("机型识别异常:%1").arg(QString::fromUtf8(e.what()))); } catch (...) { appendRecognitionFailure(QStringLiteral("机型识别发生未知异常")); } } } if (ret == 0) { algorithmState = control.nextState; } PublishDetectionResult(result); return ret == 0; } std::shared_ptr ParkingSpaceGuidePresenter::AcquireCloudFrameBuffer() { std::lock_guard dataLock(m_dataMutex); for (size_t i = 0; i < m_cloudFrameSlots.size(); ++i) { const size_t slotIndex = (m_nextCloudFrameSlot + i) % m_cloudFrameSlots.size(); std::shared_ptr& slot = m_cloudFrameSlots[slotIndex]; if (!slot) { slot = std::make_shared(); } if (slot.use_count() == 1) { m_nextCloudFrameSlot = (slotIndex + 1) % m_cloudFrameSlots.size(); return slot; } } return std::shared_ptr(); } bool ParkingSpaceGuidePresenter::CopyCloudFrame(const RsCloudData& cloud, const RsFrameInfo& info, OwnedCloudFrame& destination) { size_t totalPointCount = 0; for (const auto& item : cloud) { const SVzLaserLineData& line = item.second; if (item.first != keResultDataType_PointXYZI || line.nPointCount < 0 || (line.nPointCount > 0 && !line.p3DPoint)) { return false; } totalPointCount += static_cast(line.nPointCount); } destination.points.resize(totalPointCount); destination.cloud.clear(); destination.cloud.reserve(cloud.size()); destination.info = info; destination.timestampMs = MonotonicMilliseconds(); size_t pointOffset = 0; for (const auto& item : cloud) { SVzLaserLineData line = item.second; line.p2DPoint = nullptr; if (line.nPointCount > 0) { SVzNLPointXYZI* target = destination.points.data() + pointOffset; std::memcpy(target, line.p3DPoint, sizeof(SVzNLPointXYZI) * static_cast(line.nPointCount)); line.p3DPoint = target; pointOffset += static_cast(line.nPointCount); } else { line.p3DPoint = nullptr; } destination.cloud.emplace_back(item.first, line); } return true; } bool ParkingSpaceGuidePresenter::SubmitCloudFrame(const RsCloudData& cloud, const RsFrameInfo& info) { try { std::shared_ptr frame = AcquireCloudFrameBuffer(); if (!frame) { return false; } if (!CopyCloudFrame(cloud, info, *frame)) { if (!m_cloudCopyErrorReported.exchange(true)) { NotifyStatus("雷达点云格式非 PointXYZI,已丢弃当前帧"); } return false; } m_cloudCopyErrorReported.store(false); return SubmitOwnedCloudFrame(frame) != 0; } catch (const std::exception& e) { if (!m_cloudCopyErrorReported.exchange(true)) { NotifyStatus(std::string("复制雷达点云失败:") + e.what()); } } catch (...) { if (!m_cloudCopyErrorReported.exchange(true)) { NotifyStatus("复制雷达点云发生未知异常"); } } return false; } unsigned long long ParkingSpaceGuidePresenter::SubmitOwnedCloudFrame( const std::shared_ptr& frame) { if (!frame || (m_simulationOptions.enabled && m_stopDetectionRequested.load())) { return 0; } unsigned long long sequence = 0; { std::lock_guard dataLock(m_dataMutex); if (m_simulationOptions.enabled && m_stopDetectionRequested.load()) { return 0; } m_latestCloudFrame = frame; sequence = ++m_cloudSequence; } m_detectionCondition.notify_all(); return sequence; } bool ParkingSpaceGuidePresenter::WaitForSimulationFrameProcessed( unsigned long long sequence) { std::unique_lock dataLock(m_dataMutex); m_detectionCondition.wait(dataLock, [this, sequence]() { return m_stopDetectionRequested.load() || m_processedCloudSequence >= sequence; }); return !m_stopDetectionRequested.load() && m_processedCloudSequence >= sequence; } bool ParkingSpaceGuidePresenter::WaitForSimulationInterval(int intervalMs) { if (intervalMs <= 0) { return !m_stopDetectionRequested.load(); } std::unique_lock dataLock(m_dataMutex); const bool stopped = m_detectionCondition.wait_for( dataLock, std::chrono::milliseconds(intervalMs), [this]() { return m_stopDetectionRequested.load(); }); return !stopped; } void ParkingSpaceGuidePresenter::SimulationPlaybackLoop() { const auto failPlayback = [this](const QString& message) { const QString text = message.trimmed().isEmpty() ? QStringLiteral("仿真回放失败") : message.trimmed(); NotifyStatus(text.toUtf8().toStdString()); PublishErrorResult(-1, text); m_stopDetectionRequested.store(true); m_detectionRunning.store(false); m_trackingMode.store(false); NotifyWorkStatus(WorkStatus::Error); m_detectionCondition.notify_all(); }; bool playbackCompleted = false; size_t playbackRound = 0; while (!m_stopDetectionRequested.load()) { ++playbackRound; m_simulationRound.store( static_cast(playbackRound)); if (playbackRound > 1) { NotifyStatus(QStringLiteral("开始第 %1 轮仿真回放") .arg(playbackRound) .toUtf8() .toStdString()); } for (size_t frameIndex = 0; frameIndex < m_simulationRadarFrames.size(); ++frameIndex) { if (m_stopDetectionRequested.load()) { break; } const auto frameStartedAt = std::chrono::steady_clock::now(); const std::shared_ptr& radarFrame = m_simulationRadarFrames[frameIndex]; radarFrame->timestampMs = static_cast(frameIndex) * static_cast(m_simulationOptions.frameIntervalMs); const size_t imageIndex = frameIndex % m_simulationCameraFrames.size(); const QImage& cameraFrame = m_simulationCameraFrames[imageIndex]; CacheSimulationCameraFrame(frameIndex, cameraFrame); if (m_stopDetectionRequested.load()) { return; } const unsigned long long sequence = SubmitOwnedCloudFrame(radarFrame); if (sequence == 0) { if (m_stopDetectionRequested.load()) { return; } failPlayback(QStringLiteral("提交仿真雷达帧失败:%1") .arg(QFileInfo( m_simulationRadarFiles[frameIndex]) .fileName())); return; } if (frameIndex == 0 || frameIndex + 1 == m_simulationRadarFrames.size() || (frameIndex + 1) % 10 == 0) { NotifyStatus(QStringLiteral("仿真帧 %1/%2:%3 + %4") .arg(frameIndex + 1) .arg(m_simulationRadarFrames.size()) .arg(QFileInfo( m_simulationRadarFiles[frameIndex]) .fileName()) .arg(QFileInfo( m_simulationCameraFiles[imageIndex]) .fileName()) .toUtf8() .toStdString()); } if (!WaitForSimulationFrameProcessed(sequence)) { return; } const bool hasNextFrame = frameIndex + 1 < m_simulationRadarFrames.size(); if (hasNextFrame || m_simulationOptions.loop) { const long long elapsedMs = std::chrono::duration_cast( std::chrono::steady_clock::now() - frameStartedAt) .count(); const int remainingMs = elapsedMs >= static_cast(m_simulationOptions.frameIntervalMs) ? 0 : m_simulationOptions.frameIntervalMs - static_cast(elapsedMs); if (!WaitForSimulationInterval(remainingMs)) { return; } } } if (m_stopDetectionRequested.load()) { break; } if (!m_simulationOptions.loop) { playbackCompleted = true; break; } } if (playbackCompleted) { m_stopDetectionRequested.store(true); m_detectionRunning.store(false); m_trackingMode.store(false); m_detectionCondition.notify_all(); NotifyWorkStatus(WorkStatus::Completed); NotifyStatus(QStringLiteral("停机引导仿真回放完成,共处理 %1 帧") .arg(m_simulationRadarFrames.size()) .toUtf8() .toStdString()); } } bool ParkingSpaceGuidePresenter::ProcessAirplanePresence(const RsCloudData& cloud) { AirplanePresenceResult presence; const int ret = m_detectPresenter->DetectAirplanePresence( cloud, m_configManager->GetAlgorithmParams(), presence); if (ret != 0) { const QString message = presence.message.trimmed().isEmpty() ? QStringLiteral("飞机存在检测失败") : presence.message.trimmed(); PublishErrorResult(ret, message); return false; } if (presence.state == AirplanePresenceState::Detected) { NotifyStatus("检测到飞机,进入引导检测"); return true; } ParkingGuideState guideState = ParkingGuideState::Unknown; QString guideText; if (presence.state == AirplanePresenceState::GateBlocked) { guideState = ParkingGuideState::GateBlocked; guideText = QStringLiteral("GATEBLOCK"); } else if (presence.state == AirplanePresenceState::ViewBlocked) { guideState = ParkingGuideState::ViewBlocked; guideText = QStringLiteral("VIEWBLOCKED"); } else { return false; } DetectionResult result; ParkingSpaceGuideInfo info; info.hasException = true; info.guideStateCode = static_cast(guideState); info.guideText = guideText; result.parkingSpaceInfoList.push_back(info); result.message = presence.message.trimmed().isEmpty() ? guideText : presence.message.trimmed(); PublishDetectionResult(result); return false; } void ParkingSpaceGuidePresenter::PublishDetectionResult(DetectionResult& result) { if (m_simulationOptions.enabled && result.image.isNull()) { std::lock_guard dataLock(m_dataMutex); result.image = m_simulationCameraImage; } EnrichDetectionResult(result); if (StatusCallback()) { StatusCallback()->OnDetectionResult(result); } UpdateCurrentStatus(result); if (m_ledDisplay) { m_ledDisplay->SendResult(ConvertLedResult(result)); } BroadcastDetectionResult(result); } void ParkingSpaceGuidePresenter::PublishErrorResult(int errorCode, const QString& message) { DetectionResult result; result.errorCode = errorCode == 0 ? -1 : errorCode; result.message = message; PublishDetectionResult(result); } bool ParkingSpaceGuidePresenter::CaptureModelRecognitionImage(QImage& image, QString& errorMessage) { image = QImage(); errorMessage.clear(); if (m_simulationOptions.enabled) { std::lock_guard lock(m_dataMutex); image = m_simulationCameraImage; if (image.isNull()) { errorMessage = QStringLiteral("仿真平面相机 BMP 图像不可用"); return false; } return true; } if (!m_mvsDevice || !m_mvsDevice->IsDeviceOpen() || !m_mvsDevice->IsAcquisitioning()) { errorMessage = QStringLiteral("机型识别相机不可用,雷达引导继续"); return false; } int ret = m_mvsDevice->SendSoftTrigger(); if (ret != 0) { errorMessage = QStringLiteral("机型识别相机软触发失败:%1").arg(ret); return false; } MvsImageData captured; ret = m_mvsDevice->CaptureImage(captured, kModelCaptureTimeoutMs); std::unique_ptr capturedData(captured.pData); if (ret != 0 || !captured.pData) { errorMessage = QStringLiteral("获取机型识别图像失败:%1").arg(ret); return false; } if (!CacheMvsFrame(captured, image)) { errorMessage = QStringLiteral("机型识别图像格式无效"); return false; } return true; } void ParkingSpaceGuidePresenter::DetectionLoop() { ParkingGuideAlgorithmState algorithmState; unsigned long long activeSimulationRound = 0; while (!m_stopDetectionRequested.load()) { std::shared_ptr cloudFrame; unsigned long long frameSequence = 0; QString lidarError; { std::unique_lock dataLock(m_dataMutex); m_detectionCondition.wait(dataLock, [this]() { return m_stopDetectionRequested.load() || m_lidarErrorPending || m_cloudSequence != m_consumedCloudSequence; }); if (m_stopDetectionRequested.load()) { break; } if (m_lidarErrorPending) { lidarError = m_lidarErrorMessage; m_lidarErrorPending = false; } else { cloudFrame = m_latestCloudFrame; m_consumedCloudSequence = m_cloudSequence; frameSequence = m_consumedCloudSequence; } } if (!lidarError.isEmpty()) { PublishErrorResult(-1, lidarError); m_stopDetectionRequested.store(true); m_detectionRunning.store(false); m_trackingMode.store(false); NotifyWorkStatus(WorkStatus::Error); break; } if (!cloudFrame) { continue; } if (m_simulationOptions.enabled) { const unsigned long long currentRound = m_simulationRound.load(); if (currentRound != activeSimulationRound) { algorithmState = ParkingGuideAlgorithmState(); m_trackingMode.store(false); activeSimulationRound = currentRound; } } try { m_trackingMode.store(true); DetectOnce(cloudFrame->cloud, cloudFrame->timestampMs, algorithmState); } catch (const std::exception& e) { PublishErrorResult(-1, QStringLiteral("停机引导检测异常:%1") .arg(QString::fromUtf8(e.what()))); } catch (...) { PublishErrorResult(-1, QStringLiteral("停机引导检测发生未知异常")); } { std::lock_guard dataLock(m_dataMutex); m_processedCloudSequence = (std::max)(m_processedCloudSequence, frameSequence); } m_detectionCondition.notify_all(); } m_detectionRunning.store(false); m_trackingMode.store(false); } int ParkingSpaceGuidePresenter::StopDetection() { std::lock_guard threadLock(m_detectionThreadMutex); const bool wasRunning = m_detectionRunning.load(); { std::lock_guard dataLock(m_dataMutex); m_stopDetectionRequested.store(true); } m_detectionCondition.notify_all(); if (m_simulationThread.joinable() && m_simulationThread.get_id() != std::this_thread::get_id()) { m_simulationThread.join(); } if (m_detectionThread.joinable() && m_detectionThread.get_id() != std::this_thread::get_id()) { m_detectionThread.join(); } m_detectionRunning.store(false); m_trackingMode.store(false); m_lastProbeAcceptedMs.store(0); if (wasRunning) { NotifyWorkStatus(WorkStatus::Ready); NotifyStatus(m_simulationOptions.enabled ? "停机引导仿真回放已停止" : "停机引导实时检测已停止"); } return 0; } bool ParkingSpaceGuidePresenter::IsDetectionRunning() const { return m_detectionRunning.load(); } int ParkingSpaceGuidePresenter::LoadAndDetect(const QString& fileName) { Q_UNUSED(fileName); return TriggerDetection(m_currentCameraIndex) ? 0 : -1; } int ParkingSpaceGuidePresenter::SaveDetectionDataToFile(const std::string& filePath) { std::lock_guard lock(m_dataMutex); std::ofstream file(filePath.c_str(), std::ios::out | std::ios::trunc); if (!file.is_open()) { return -1; } file << "ParkingSpaceGuide cached data\n"; file << "hasFrame=" << (m_hasFrame ? 1 : 0) << "\n"; file << "frameWidth=" << m_latestFrameInfo.width << "\n"; file << "frameHeight=" << m_latestFrameInfo.height << "\n"; file << "hasCloud=" << (m_latestCloudFrame ? 1 : 0) << "\n"; file << "cloudLines=" << (m_latestCloudFrame ? m_latestCloudFrame->cloud.size() : 0) << "\n"; return 0; } int ParkingSpaceGuidePresenter::GetDetectionDataCacheSize() const { std::lock_guard lock(m_dataMutex); return (m_hasFrame ? 1 : 0) + (m_latestCloudFrame ? 1 : 0); } void ParkingSpaceGuidePresenter::SetDefaultCameraIndex(int cameraIndex) { m_currentCameraIndex = (std::max)(1, cameraIndex); } int ParkingSpaceGuidePresenter::GetDetectIndex() const { return m_detectIndex; } QString ParkingSpaceGuidePresenter::GetAlgoVersion() const { return DetectPresenter::GetAlgoVersion(); } ConfigManager* ParkingSpaceGuidePresenter::GetConfigManager() { return m_configManager; } std::vector> ParkingSpaceGuidePresenter::GetCameraList() const { return m_cameraList; } void ParkingSpaceGuidePresenter::OnConfigChanged(const ConfigResult& configResult) { std::lock_guard reconfigurationLock(m_reconfigurationMutex); if (!m_initialized) { return; } m_reconfiguring.store(true); const bool restartDetection = m_detectionRunning.load(); const int detectIndex = m_detectIndex; StopDetection(); NotifyStatus(m_simulationOptions.enabled ? "配置已变更,正在重新初始化输出协议" : "配置已变更,正在重新连接设备"); if (!m_simulationOptions.enabled) { InitMvsCamera(configResult.mvsCamera); InitRsLidar(configResult.lidarConfig); } InitLedDisplay(configResult.ledDisplayConfig); InitUdpBroadcast(configResult.udpBroadcastConfig); InitHttpServer(configResult.httpServerConfig); m_reconfiguring.store(false); if (restartDetection) { StartDetection(detectIndex); } } IYParkingSpaceGuideStatus* ParkingSpaceGuidePresenter::StatusCallback() const { return static_cast(m_statusCallback); } int ParkingSpaceGuidePresenter::InitConfig() { if (!m_configManager) { m_configManager = new ConfigManager(); } if (!m_configManager->Initialize(PathManager::GetInstance().GetConfigFilePath().toStdString())) { NotifyStatus("初始化配置管理器失败"); return -1; } ConfigResult config = m_configManager->GetConfigResult(); config.Normalize(); SystemConfig systemConfig = m_configManager->GetConfig(); systemConfig.configResult = config; m_configManager->UpdateFullConfig(systemConfig); return 0; } int ParkingSpaceGuidePresenter::InitSimulationData() { ClearSimulationData(); const QFileInfo directoryInfo(m_simulationOptions.dataDirectory); if (!directoryInfo.exists() || !directoryInfo.isDir()) { NotifyStatus(QStringLiteral("仿真数据目录不存在或不是目录:%1") .arg(directoryInfo.absoluteFilePath()) .toUtf8() .toStdString()); return -1; } const QDir directory(directoryInfo.absoluteFilePath()); const QFileInfoList entries = directory.entryInfoList( QDir::Files | QDir::Readable | QDir::NoDotAndDotDot, QDir::Name); for (const QFileInfo& entry : entries) { const QString suffix = entry.suffix(); if (suffix.compare(QStringLiteral("txt"), Qt::CaseInsensitive) == 0) { m_simulationRadarFiles.push_back(entry.absoluteFilePath()); } else if (suffix.compare(QStringLiteral("bmp"), Qt::CaseInsensitive) == 0) { m_simulationCameraFiles.push_back(entry.absoluteFilePath()); } } SortPathsNaturally(m_simulationRadarFiles); SortPathsNaturally(m_simulationCameraFiles); if (m_simulationRadarFiles.size() < 2) { NotifyStatus("仿真数据目录至少需要两个 TXT 雷达帧"); ClearSimulationData(); return -1; } if (m_simulationCameraFiles.empty()) { NotifyStatus("仿真数据目录至少需要一个 BMP 平面相机帧"); ClearSimulationData(); return -1; } NotifyStatus(QStringLiteral("开始将仿真数据加载到内存:%1 个点云,%2 个图像") .arg(m_simulationRadarFiles.size()) .arg(m_simulationCameraFiles.size()) .toUtf8() .toStdString()); QString errorMessage; try { m_simulationRadarFrames.reserve(m_simulationRadarFiles.size()); for (size_t frameIndex = 0; frameIndex < m_simulationRadarFiles.size(); ++frameIndex) { std::shared_ptr radarFrame; if (!LoadSimulationRadarFrame(frameIndex, radarFrame, errorMessage)) { NotifyStatus(QStringLiteral("加载仿真点云失败:%1") .arg(errorMessage) .toUtf8() .toStdString()); ClearSimulationData(); return -1; } m_simulationRadarFrames.push_back(radarFrame); if (frameIndex == 0 || frameIndex + 1 == m_simulationRadarFiles.size() || (frameIndex + 1) % 10 == 0) { NotifyStatus(QStringLiteral("点云加载进度:%1/%2") .arg(frameIndex + 1) .arg(m_simulationRadarFiles.size()) .toUtf8() .toStdString()); } } m_simulationCameraFrames.reserve(m_simulationCameraFiles.size()); for (size_t imageIndex = 0; imageIndex < m_simulationCameraFiles.size(); ++imageIndex) { QImage cameraFrame; if (!LoadSimulationCameraFile(imageIndex, cameraFrame, errorMessage)) { NotifyStatus(QStringLiteral("加载仿真图像失败:%1") .arg(errorMessage) .toUtf8() .toStdString()); ClearSimulationData(); return -1; } m_simulationCameraFrames.push_back(cameraFrame); if (imageIndex == 0 || imageIndex + 1 == m_simulationCameraFiles.size() || (imageIndex + 1) % 10 == 0) { NotifyStatus(QStringLiteral("图像加载进度:%1/%2") .arg(imageIndex + 1) .arg(m_simulationCameraFiles.size()) .toUtf8() .toStdString()); } } } catch (const std::exception& e) { NotifyStatus(QStringLiteral("仿真数据加载到内存失败:%1") .arg(QString::fromUtf8(e.what())) .toUtf8() .toStdString()); ClearSimulationData(); return -1; } catch (...) { NotifyStatus("仿真数据加载到内存时发生未知异常"); ClearSimulationData(); return -1; } { std::lock_guard dataLock(m_dataMutex); m_latestCloudFrame = m_simulationRadarFrames.front(); } CacheSimulationCameraFrame(0, m_simulationCameraFrames.front()); m_cameraList.clear(); m_cameraList.push_back(std::make_pair( std::string("仿真平面相机"), static_cast(nullptr))); NotifyCameraStatus(true); NotifyLidarStatus(true); NotifyStatus(QStringLiteral("仿真数据已全部加载到内存:%1 个点云帧,%2 个图像帧,图像循环配对,周期 %3 ms%4") .arg(m_simulationRadarFrames.size()) .arg(m_simulationCameraFrames.size()) .arg(m_simulationOptions.frameIntervalMs) .arg(m_simulationOptions.loop ? QStringLiteral(",循环回放") : QStringLiteral(",单次回放")) .toUtf8() .toStdString()); return 0; } void ParkingSpaceGuidePresenter::ClearSimulationData() { m_simulationRadarFiles.clear(); m_simulationCameraFiles.clear(); m_simulationRadarFrames.clear(); m_simulationCameraFrames.clear(); m_cameraList.clear(); std::lock_guard dataLock(m_dataMutex); m_simulationCameraImage = QImage(); m_latestFrame.clear(); m_latestFrameInfo = CameraFrameInfo(); m_hasFrame = false; m_latestCloudFrame.reset(); for (std::shared_ptr& slot : m_cloudFrameSlots) { slot.reset(); } m_nextCloudFrameSlot = 0; m_cloudSequence = 0; m_consumedCloudSequence = 0; m_processedCloudSequence = 0; m_lidarErrorPending = false; m_lidarErrorMessage.clear(); } bool ParkingSpaceGuidePresenter::LoadSimulationRadarFrame( size_t frameIndex, std::shared_ptr& frame, QString& errorMessage) { frame.reset(); errorMessage.clear(); if (frameIndex >= m_simulationRadarFiles.size()) { errorMessage = QStringLiteral("雷达帧序号越界:%1").arg(frameIndex); return false; } const QString filePath = m_simulationRadarFiles[frameIndex]; LaserDataLoader loader; std::vector> loadedData; int lineCount = 0; float scanSpeed = 0.0f; int maxTimestamp = 0; int clocksPerSecond = 0; int ret = -1; try { ret = loader.LoadLaserScanData(QFile::encodeName(filePath).toStdString(), loadedData, lineCount, scanSpeed, maxTimestamp, clocksPerSecond); } catch (const std::exception& e) { errorMessage = QStringLiteral("加载 %1 异常:%2") .arg(QFileInfo(filePath).fileName(), QString::fromUtf8(e.what())); loader.FreeLaserScanData(loadedData); return false; } catch (...) { errorMessage = QStringLiteral("加载 %1 时发生未知异常") .arg(QFileInfo(filePath).fileName()); loader.FreeLaserScanData(loadedData); return false; } if (ret != 0) { errorMessage = QStringLiteral("%1:%2") .arg(QFileInfo(filePath).fileName(), QString::fromStdString(loader.GetLastError())); loader.FreeLaserScanData(loadedData); return false; } if (loadedData.empty()) { errorMessage = QStringLiteral("%1 未包含扫描线") .arg(QFileInfo(filePath).fileName()); loader.FreeLaserScanData(loadedData); return false; } size_t totalPointCount = 0; int maximumLinePointCount = 0; for (const auto& item : loadedData) { const SVzLaserLineData& line = item.second; if (line.nPointCount < 0 || (line.nPointCount > 0 && !line.p3DPoint)) { errorMessage = QStringLiteral("%1 包含无效扫描线") .arg(QFileInfo(filePath).fileName()); loader.FreeLaserScanData(loadedData); return false; } if (item.first != keResultDataType_Position && item.first != keResultDataType_PointXYZI && item.first != keResultDataType_PointXYZRGBA) { errorMessage = QStringLiteral("%1 的点类型不支持:%2") .arg(QFileInfo(filePath).fileName()) .arg(static_cast(item.first)); loader.FreeLaserScanData(loadedData); return false; } const size_t pointCount = static_cast(line.nPointCount); if (pointCount > (std::numeric_limits::max)() - totalPointCount) { errorMessage = QStringLiteral("%1 的点数量溢出") .arg(QFileInfo(filePath).fileName()); loader.FreeLaserScanData(loadedData); return false; } totalPointCount += pointCount; maximumLinePointCount = (std::max)(maximumLinePointCount, line.nPointCount); } if (totalPointCount == 0) { errorMessage = QStringLiteral("%1 不包含有效点") .arg(QFileInfo(filePath).fileName()); loader.FreeLaserScanData(loadedData); return false; } try { std::shared_ptr converted = std::make_shared(); converted->points.resize(totalPointCount); std::memset(converted->points.data(), 0, sizeof(SVzNLPointXYZI) * totalPointCount); converted->cloud.reserve(loadedData.size()); size_t pointOffset = 0; for (const auto& item : loadedData) { const SVzLaserLineData& sourceLine = item.second; SVzLaserLineData targetLine = sourceLine; targetLine.p2DPoint = nullptr; targetLine.p3DPoint = sourceLine.nPointCount > 0 ? converted->points.data() + pointOffset : nullptr; for (int pointIndex = 0; pointIndex < sourceLine.nPointCount; ++pointIndex) { SVzNLPointXYZI& targetPoint = converted->points[pointOffset + static_cast(pointIndex)]; if (item.first == keResultDataType_Position) { const auto* sourcePoints = static_cast(sourceLine.p3DPoint); targetPoint.x = static_cast(sourcePoints[pointIndex].pt3D.x); targetPoint.y = static_cast(sourcePoints[pointIndex].pt3D.y); targetPoint.z = static_cast(sourcePoints[pointIndex].pt3D.z); targetPoint.intensity = 0.0f; } else if (item.first == keResultDataType_PointXYZRGBA) { const auto* sourcePoints = static_cast(sourceLine.p3DPoint); targetPoint.x = sourcePoints[pointIndex].x; targetPoint.y = sourcePoints[pointIndex].y; targetPoint.z = sourcePoints[pointIndex].z; targetPoint.intensity = 0.0f; } else { const auto* sourcePoints = static_cast(sourceLine.p3DPoint); targetPoint = sourcePoints[pointIndex]; } } converted->cloud.emplace_back(keResultDataType_PointXYZI, targetLine); pointOffset += static_cast(sourceLine.nPointCount); } converted->info.height = static_cast(loadedData.size()); converted->info.width = static_cast(maximumLinePointCount); converted->info.isDense = false; frame = converted; } catch (const std::exception& e) { errorMessage = QStringLiteral("转换 %1 失败:%2") .arg(QFileInfo(filePath).fileName(), QString::fromUtf8(e.what())); loader.FreeLaserScanData(loadedData); return false; } catch (...) { errorMessage = QStringLiteral("转换 %1 时发生未知异常") .arg(QFileInfo(filePath).fileName()); loader.FreeLaserScanData(loadedData); return false; } loader.FreeLaserScanData(loadedData); return true; } bool ParkingSpaceGuidePresenter::LoadSimulationCameraFile( size_t imageIndex, QImage& image, QString& errorMessage) { image = QImage(); errorMessage.clear(); if (imageIndex >= m_simulationCameraFiles.size()) { errorMessage = QStringLiteral("图像帧序号越界:%1").arg(imageIndex); return false; } const QString filePath = m_simulationCameraFiles[imageIndex]; QImageReader reader(filePath, "bmp"); reader.setAutoTransform(true); image = reader.read(); if (image.isNull()) { errorMessage = QStringLiteral("%1:%2") .arg(QFileInfo(filePath).fileName(), reader.errorString()); return false; } if (image.format() != QImage::Format_RGB888) { image = image.convertToFormat(QImage::Format_RGB888); } if (image.isNull()) { errorMessage = QStringLiteral("%1 转换为 RGB888 失败") .arg(QFileInfo(filePath).fileName()); return false; } return true; } void ParkingSpaceGuidePresenter::CacheSimulationCameraFrame( size_t frameIndex, const QImage& image) { const QImage rgbImage = image.format() == QImage::Format_RGB888 ? image : image.convertToFormat(QImage::Format_RGB888); CameraFrameInfo frameInfo; frameInfo.width = rgbImage.width(); frameInfo.height = rgbImage.height(); frameInfo.pixelFormat = kMvsPixelTypeRGB8; frameInfo.frameId = static_cast(frameIndex + 1); frameInfo.timestamp = static_cast( QDateTime::currentMSecsSinceEpoch()); std::lock_guard dataLock(m_dataMutex); m_simulationCameraImage = rgbImage; m_latestFrame.clear(); m_latestFrameInfo = frameInfo; m_hasFrame = !rgbImage.isNull(); } int ParkingSpaceGuidePresenter::InitMvsCamera(const MvsCameraConfig& config) { auto closeMvsDevice = [this]() { if (!m_mvsDevice) { return; } m_mvsDevice->StopAcquisition(); m_mvsDevice->UnregisterImageCallback(); m_mvsDevice->CloseDevice(); m_mvsDevice->UninitSDK(); delete m_mvsDevice; m_mvsDevice = nullptr; }; closeMvsDevice(); m_cameraList.clear(); { std::lock_guard lock(m_dataMutex); m_latestFrame.clear(); m_latestFrameInfo = CameraFrameInfo(); m_hasFrame = false; } if (!config.enabled) { NotifyCameraStatus(false); NotifyStatus("平面相机未启用"); m_cameraList.push_back(std::make_pair(std::string("平面相机"), static_cast(nullptr))); return 0; } if (IMvsDevice::CreateObject(&m_mvsDevice) != 0 || !m_mvsDevice) { NotifyCameraStatus(false); NotifyStatus("创建平面相机设备失败"); return -1; } int ret = m_mvsDevice->InitSDK(); if (ret != 0) { NotifyCameraStatus(false); NotifyStatus("平面相机SDK初始化失败:" + std::to_string(ret)); closeMvsDevice(); return ret; } std::vector devices; ret = m_mvsDevice->EnumerateDevices(devices); if (ret != 0 || devices.empty()) { NotifyCameraStatus(false); NotifyStatus(ret != 0 ? "搜索平面相机失败:" + std::to_string(ret) : "未搜索到平面相机"); closeMvsDevice(); return ret != 0 ? ret : -1; } NotifyStatus("搜索到平面相机数量:" + std::to_string(devices.size())); if (!config.serialNumber.empty()) { ret = m_mvsDevice->OpenDevice(config.serialNumber); } else { unsigned int deviceIndex = static_cast((std::max)(0, config.deviceIndex)); if (deviceIndex >= devices.size()) { NotifyStatus("相机设备序号超出范围,使用第0台设备"); deviceIndex = 0; } ret = m_mvsDevice->OpenDeviceByIndex(deviceIndex); } if (ret != 0) { NotifyCameraStatus(false); NotifyStatus("打开平面相机失败:" + std::to_string(ret)); closeMvsDevice(); return ret; } MvsDeviceInfo openedInfo; std::string cameraName = config.serialNumber.empty() ? std::string("平面相机") : config.serialNumber; if (m_mvsDevice->GetDeviceInfo(openedInfo) == 0) { if (!openedInfo.displayName.empty()) { cameraName = openedInfo.displayName; } else if (!openedInfo.serialNumber.empty()) { cameraName = openedInfo.serialNumber; } } ret = m_mvsDevice->SetEnumFeature("AcquisitionMode", kMvsAcqModeContinuous); if (ret != 0) { NotifyStatus("设置平面相机连续采集模式失败,继续使用当前模式:" + std::to_string(ret)); } ret = m_mvsDevice->SetEnumFeature("PixelFormat", kMvsPixelTypeMono8); if (ret != 0) { NotifyStatus("设置平面相机灰度格式失败,继续使用当前格式:" + std::to_string(ret)); } ret = m_mvsDevice->SetTriggerMode(true); if (ret != 0) { NotifyCameraStatus(false); NotifyStatus("启用机型识别相机触发模式失败:" + std::to_string(ret)); closeMvsDevice(); return ret; } ret = m_mvsDevice->SetEnumFeatureByString("TriggerSource", "Software"); if (ret != 0) { NotifyCameraStatus(false); NotifyStatus("设置机型识别相机软触发源失败:" + std::to_string(ret)); closeMvsDevice(); return ret; } ret = m_mvsDevice->StartAcquisition(); if (ret != 0) { NotifyCameraStatus(false); NotifyStatus("启动平面相机采集失败:" + std::to_string(ret)); closeMvsDevice(); return ret; } NotifyCameraStatus(true); m_cameraList.push_back(std::make_pair(cameraName, static_cast(m_mvsDevice))); NotifyStatus("平面相机已连接:" + cameraName); return 0; } int ParkingSpaceGuidePresenter::InitRsLidar(const RsLidarConfig& config) { if (m_lidarDevice) { m_lidarDevice->Stop(); m_lidarDevice->CloseDevice(); delete m_lidarDevice; m_lidarDevice = nullptr; } { std::lock_guard dataLock(m_dataMutex); m_latestCloudFrame.reset(); for (std::shared_ptr& slot : m_cloudFrameSlots) { slot.reset(); } m_nextCloudFrameSlot = 0; m_cloudSequence = 0; m_consumedCloudSequence = 0; m_processedCloudSequence = 0; m_lidarErrorPending = false; m_lidarErrorMessage.clear(); } m_trackingMode.store(false); m_lastProbeAcceptedMs.store(0); m_cloudCopyErrorReported.store(false); if (IRsLidarDevice::CreateObject(&m_lidarDevice) != 0 || !m_lidarDevice) { NotifyLidarStatus(false); return -1; } m_lidarDevice->SetPointCloudCallback([this](const RsCloudData& cloud, const RsFrameInfo& info) { if (!m_trackingMode.load()) { const long long nowMs = std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch()).count(); const long long lastMs = m_lastProbeAcceptedMs.load(); if (lastMs > 0 && nowMs - lastMs < kAirplaneProbeIntervalMs) { return; } m_lastProbeAcceptedMs.store(nowMs); } SubmitCloudFrame(cloud, info); }); m_lidarDevice->SetExceptionCallback([this](const RsExceptionInfo& info) { const QString message = info.message.empty() ? QStringLiteral("雷达异常") : QStringLiteral("雷达异常:%1").arg(QString::fromStdString(info.message)); const bool fatal = info.code == 0x40 || info.code >= 0x80; if (fatal && m_detectionRunning.load()) { std::lock_guard dataLock(m_dataMutex); m_lidarErrorPending = true; m_lidarErrorMessage = message; } if (fatal) { m_detectionCondition.notify_all(); } NotifyStatus(message.toUtf8().toStdString()); if (fatal) { NotifyLidarStatus(false); } }); int ret = m_lidarDevice->InitDevice(); if (ret != 0) { NotifyLidarStatus(false); return ret; } ret = m_lidarDevice->OpenDevice(config); if (ret != 0) { NotifyLidarStatus(false); return ret; } ret = m_lidarDevice->Start(); NotifyLidarStatus(ret == 0); return ret; } int ParkingSpaceGuidePresenter::InitLedDisplay(const LedDisplayConfig& config) { if (m_ledDisplay) { m_ledDisplay->Close(); delete m_ledDisplay; m_ledDisplay = nullptr; } if (ILedDisplayDevice::CreateObject(&m_ledDisplay) != 0 || !m_ledDisplay) { NotifyLedStatus(false); return -1; } m_ledDisplay->SetStatusCallback([](ELedDisplayStatus status, const std::string& message, void* user) { auto* self = static_cast(user); if (!self) { return; } self->NotifyLedStatus(status == ELedDisplayStatus::Connected); if (!message.empty()) { self->NotifyStatus(std::string("显示屏:") + message); } }, this); const int ret = m_ledDisplay->Open(config); NotifyLedStatus(config.enabled && ret == 0 && m_ledDisplay->IsConnected()); return ret; } int ParkingSpaceGuidePresenter::InitUdpBroadcast(const UdpBroadcastConfig& config) { std::lock_guard lock(m_udpMutex); if (m_udpClient) { m_udpClient->StopUDPClient(); delete m_udpClient; m_udpClient = nullptr; } m_udpBroadcastConfig = config; if (!config.enabled) { NotifyStatus("组播未启用"); return 0; } if (config.address.empty() || config.port <= 0 || config.port > 65535) { NotifyStatus("组播配置无效"); return -1; } if (!IYUDPClient::CreateUDPClient(&m_udpClient) || !m_udpClient) { NotifyStatus("创建组播客户端失败"); return -1; } const int ret = m_udpClient->Init(false); if (ret != 0) { NotifyStatus("初始化组播客户端失败:" + std::to_string(ret)); m_udpClient->StopUDPClient(); delete m_udpClient; m_udpClient = nullptr; return ret; } NotifyStatus("组播客户端已初始化"); return 0; } int ParkingSpaceGuidePresenter::InitHttpServer(const HttpServerConfig& config) { CloseHttpServer(); m_httpServer = new ParkingStatusHttpServer(); const int ret = m_httpServer->Start( config, [this]() { return CurrentStatusJson(); }); if (ret != 0) { const std::string error = m_httpServer->LastError(); delete m_httpServer; m_httpServer = nullptr; NotifyStatus(error.empty() ? "HTTP状态服务启动失败" : "HTTP状态服务启动失败:" + error); return ret; } if (config.enabled) { NotifyStatus("HTTP状态服务已启动:" + config.address + ":" + std::to_string(config.port)); } else { NotifyStatus("HTTP状态服务未启用"); } return 0; } void ParkingSpaceGuidePresenter::CloseUdpBroadcast() { std::lock_guard lock(m_udpMutex); if (!m_udpClient) { return; } m_udpClient->StopUDPClient(); delete m_udpClient; m_udpClient = nullptr; } void ParkingSpaceGuidePresenter::CloseHttpServer() { if (!m_httpServer) { return; } m_httpServer->Stop(); delete m_httpServer; m_httpServer = nullptr; } void ParkingSpaceGuidePresenter::CloseDevices() { CloseHttpServer(); CloseUdpBroadcast(); if (m_mvsDevice) { m_mvsDevice->StopAcquisition(); m_mvsDevice->UnregisterImageCallback(); m_mvsDevice->CloseDevice(); m_mvsDevice->UninitSDK(); delete m_mvsDevice; m_mvsDevice = nullptr; } if (m_lidarDevice) { m_lidarDevice->Stop(); m_lidarDevice->CloseDevice(); delete m_lidarDevice; m_lidarDevice = nullptr; } if (m_ledDisplay) { m_ledDisplay->Close(); delete m_ledDisplay; m_ledDisplay = nullptr; } if (m_simulationOptions.enabled) { ClearSimulationData(); } } void ParkingSpaceGuidePresenter::EnrichDetectionResult(DetectionResult& result) const { const UdpBroadcastConfig config = m_configManager ? m_configManager->GetConfigResult().udpBroadcastConfig : m_udpBroadcastConfig; for (ParkingSpaceGuideInfo& info : result.parkingSpaceInfoList) { if (info.targetId.trimmed().isEmpty()) { info.targetId = ConfigText(config.targetId, QStringLiteral("T001")); } if (info.parkId.trimmed().isEmpty()) { info.parkId = ConfigText(config.parkId, QStringLiteral("P01")); } if (info.modelType.trimmed().isEmpty()) { info.modelType = QStringLiteral("未知"); } } } void ParkingSpaceGuidePresenter::UpdateCurrentStatus(const DetectionResult& result) { const UdpBroadcastConfig config = m_configManager ? m_configManager->GetConfigResult().udpBroadcastConfig : m_udpBroadcastConfig; const QByteArray data = QJsonDocument(BuildDetectionResultPayload(result, config)) .toJson(QJsonDocument::Compact); std::lock_guard lock(m_statusMutex); m_currentStatusJson.assign(data.constData(), static_cast(data.size())); } std::string ParkingSpaceGuidePresenter::CurrentStatusJson() const { std::lock_guard lock(m_statusMutex); if (!m_currentStatusJson.empty()) { return m_currentStatusJson; } return "{\"success\":false,\"hasResult\":false,\"message\":\"暂无检测结果\",\"errorCode\":-1}"; } bool ParkingSpaceGuidePresenter::BroadcastDetectionResult(const DetectionResult& result) { Q_UNUSED(result); std::lock_guard lock(m_udpMutex); if (!m_udpClient || !m_udpBroadcastConfig.enabled) { return false; } const std::string currentStatus = CurrentStatusJson(); const QByteArray data(currentStatus.data(), static_cast(currentStatus.size())); const int ret = m_udpClient->SendData(m_udpBroadcastConfig.port, m_udpBroadcastConfig.address.c_str(), data.constData(), data.size()); if (ret != 0) { NotifyStatus("组播发送失败:" + std::to_string(ret)); return false; } return true; } void ParkingSpaceGuidePresenter::NotifyStatus(const std::string& message) { LOG_INFO("%s\n", message.c_str()); if (StatusCallback()) { StatusCallback()->OnStatusUpdate(message); } } void ParkingSpaceGuidePresenter::NotifyWorkStatus(WorkStatus status) { if (StatusCallback()) { StatusCallback()->OnWorkStatusChanged(status); } } void ParkingSpaceGuidePresenter::NotifyCameraStatus(bool connected) { if (StatusCallback()) { StatusCallback()->OnCamera1StatusChanged(connected); } } void ParkingSpaceGuidePresenter::NotifyLidarStatus(bool connected) { if (StatusCallback()) { StatusCallback()->OnCamera2StatusChanged(connected); } } void ParkingSpaceGuidePresenter::NotifyLedStatus(bool connected) { if (StatusCallback()) { StatusCallback()->OnSerialConnectionChanged(connected); } } LedDisplayResult ParkingSpaceGuidePresenter::ConvertLedResult(const DetectionResult& result) const { LedDisplayResult output; if (result.errorCode != 0) { output.valid = true; output.guideCode = 2; return output; } output.valid = !result.parkingSpaceInfoList.empty(); if (!output.valid) { return output; } const ParkingSpaceGuideInfo& info = result.parkingSpaceInfoList.front(); output.guideCode = (info.hasException || IsExceptionGuideState(info.guideStateCode)) ? 2 : 1; output.distance = static_cast(info.distance); output.lateralOffset = static_cast(info.lateralOffset); output.angle = static_cast(info.angle); return output; } bool ParkingSpaceGuidePresenter::CacheMvsFrame(const MvsImageData& image, QImage& frame) { frame = QImage(); if (!image.pData || image.width == 0 || image.height == 0 || image.dataSize == 0) { return false; } const int width = static_cast(image.width); const int height = static_cast(image.height); const size_t pixelCount = static_cast(width) * static_cast(height); if (pixelCount == 0) { return false; } QImage rgbImage; if (image.pixelFormat == kMvsPixelTypeRGB8 && image.dataSize >= pixelCount * 3) { QImage src(image.pData, width, height, width * 3, QImage::Format_RGB888); rgbImage = src.copy(); } else if (image.pixelFormat == kMvsPixelTypeBGR8 && image.dataSize >= pixelCount * 3) { QImage src(image.pData, width, height, width * 3, QImage::Format_RGB888); rgbImage = src.rgbSwapped(); } else if (image.pixelFormat == kMvsPixelTypeMono8 && image.dataSize >= pixelCount) { QImage src(image.pData, width, height, width, QImage::Format_Grayscale8); rgbImage = src.convertToFormat(QImage::Format_RGB888); } else { return false; } if (rgbImage.isNull()) { return false; } std::vector compact(pixelCount * 3); for (int row = 0; row < height; ++row) { std::memcpy(compact.data() + static_cast(row) * static_cast(width) * 3, rgbImage.constScanLine(row), static_cast(width) * 3); } CameraFrameInfo frameInfo; frameInfo.width = width; frameInfo.height = height; frameInfo.pixelFormat = image.pixelFormat; frameInfo.frameId = image.frameID; frameInfo.timestamp = image.timestamp; std::lock_guard lock(m_dataMutex); m_latestFrame.swap(compact); m_latestFrameInfo = frameInfo; m_hasFrame = true; frame = rgbImage; return true; }