1317 lines
42 KiB
C++
Raw Normal View History

2026-07-11 16:02:57 +08:00
#include "ParkingSpaceGuidePresenter.h"
#include <algorithm>
#include <chrono>
2026-07-11 16:02:57 +08:00
#include <cstring>
#include <exception>
2026-07-11 16:02:57 +08:00
#include <fstream>
#include <QDateTime>
#include <QJsonDocument>
#include <QJsonObject>
#include "DetectPresenter.h"
#include "IYUDPClient.h"
#include "ParkingStatusHttpServer.h"
#include "PathManager.h"
#include "Version.h"
#include "VrLog.h"
namespace {
constexpr int kMvsPixelTypeMono8 = 0x01080001;
2026-07-14 17:14:55 +08:00
constexpr int kMvsPixelTypeRGB8 = 0x02180014;
constexpr int kMvsPixelTypeBGR8 = 0x02180015;
2026-07-11 16:02:57 +08:00
constexpr int kMvsAcqModeContinuous = 2;
2026-07-14 17:14:55 +08:00
constexpr int kAirplaneProbeIntervalMs = 1000;
constexpr unsigned int kModelCaptureTimeoutMs = 1000;
2026-07-11 16:02:57 +08:00
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)
: QObject(parent)
{
}
ParkingSpaceGuidePresenter::~ParkingSpaceGuidePresenter()
{
DeinitApp();
}
int ParkingSpaceGuidePresenter::Init()
{
m_reconfiguring.store(false);
2026-07-11 16:02:57 +08:00
NotifyWorkStatus(WorkStatus::InitIng);
NotifyStatus("停机引导初始化中");
2026-07-11 16:02:57 +08:00
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);
m_detectPresenter = new DetectPresenter();
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<void*>(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);
}
m_initialized = true;
NotifyWorkStatus(WorkStatus::Ready);
NotifyStatus("停机引导初始化完成");
2026-07-11 16:02:57 +08:00
return 0;
}
void ParkingSpaceGuidePresenter::DeinitApp()
{
m_initialized.store(false);
{
std::lock_guard<std::mutex> reconfigurationLock(m_reconfigurationMutex);
m_reconfiguring.store(true);
StopDetection();
CloseDevices();
2026-07-11 16:02:57 +08:00
delete m_detectPresenter;
m_detectPresenter = nullptr;
}
2026-07-11 16:02:57 +08:00
if (m_configManager) {
m_configManager->Shutdown();
delete m_configManager;
m_configManager = nullptr;
}
}
bool ParkingSpaceGuidePresenter::StartDetection(int cameraIndex)
{
std::lock_guard<std::mutex> threadLock(m_detectionThreadMutex);
if (!m_initialized || m_reconfiguring.load() || !m_detectPresenter || !m_configManager) {
NotifyStatus("系统未初始化");
return false;
}
2026-07-14 17:14:55 +08:00
if (!m_lidarDevice || !m_lidarDevice->IsRunning()) {
NotifyStatus("雷达未连接,无法启动实时检测");
return false;
}
2026-07-11 16:02:57 +08:00
if (m_detectionRunning.load()) {
NotifyStatus("停机引导实时检测已在运行");
return true;
}
if (m_detectionThread.joinable()) {
m_detectionThread.join();
}
if (cameraIndex > 0) {
m_detectIndex = cameraIndex;
} else {
m_detectIndex = m_currentCameraIndex;
}
2026-07-14 17:14:55 +08:00
{
std::lock_guard<std::mutex> dataLock(m_dataMutex);
m_consumedCloudSequence = m_cloudSequence;
m_lidarErrorPending = false;
m_lidarErrorMessage.clear();
}
m_trackingMode.store(false);
m_lastProbeAcceptedMs.store(0);
m_stopDetectionRequested.store(false);
m_detectionRunning.store(true);
try {
m_detectionThread = std::thread(&ParkingSpaceGuidePresenter::DetectionLoop, this);
} catch (...) {
m_detectionRunning.store(false);
m_stopDetectionRequested.store(true);
NotifyWorkStatus(WorkStatus::Error);
NotifyStatus("启动停机引导实时检测线程失败");
return false;
}
NotifyWorkStatus(WorkStatus::Working);
NotifyStatus("停机引导实时检测已开始");
return true;
2026-07-11 16:02:57 +08:00
}
bool ParkingSpaceGuidePresenter::TriggerDetection(int cameraIndex)
{
std::lock_guard<std::mutex> threadLock(m_detectionThreadMutex);
if (!m_initialized || m_reconfiguring.load() || !m_detectPresenter || !m_configManager) {
2026-07-11 16:02:57 +08:00
NotifyStatus("系统未初始化");
return false;
}
if (m_detectionRunning.load()) {
NotifyStatus("实时检测运行中,无法执行单次检测");
return false;
}
2026-07-11 16:02:57 +08:00
if (cameraIndex > 0) {
m_detectIndex = cameraIndex;
} else {
m_detectIndex = m_currentCameraIndex;
}
2026-07-14 17:14:55 +08:00
std::shared_ptr<const OwnedCloudFrame> cloudFrame;
{
std::lock_guard<std::mutex> dataLock(m_dataMutex);
cloudFrame = m_latestCloudFrame;
}
if (!cloudFrame) {
NotifyStatus("暂无可用雷达点云");
return false;
}
NotifyWorkStatus(WorkStatus::Detecting);
NotifyStatus("停机引导单次检测已开始");
bool success = false;
2026-07-14 17:14:55 +08:00
ParkingGuideAlgorithmState algorithmState;
try {
2026-07-14 17:14:55 +08:00
success = DetectOnce(cloudFrame->cloud, algorithmState);
} catch (const std::exception& e) {
NotifyStatus(std::string("停机引导单次检测异常:") + e.what());
} catch (...) {
NotifyStatus("停机引导单次检测发生未知异常");
}
NotifyWorkStatus(success ? WorkStatus::Completed : WorkStatus::Error);
NotifyStatus(success ? "停机引导单次检测完成"
: "停机引导单次检测失败");
return success;
}
2026-07-14 17:14:55 +08:00
bool ParkingSpaceGuidePresenter::DetectOnce(const RsCloudData& cloud,
ParkingGuideAlgorithmState& algorithmState)
{
std::lock_guard<std::mutex> executionLock(m_detectionExecutionMutex);
if (!m_detectPresenter || !m_configManager) {
return false;
}
2026-07-11 16:02:57 +08:00
DetectionResult result;
2026-07-14 17:14:55 +08:00
ParkingGuideAlgorithmControl control;
2026-07-11 16:02:57 +08:00
result.cameraIndex = m_detectIndex;
2026-07-14 17:14:55 +08:00
const int ret = m_detectPresenter->DetectParkingSpaceGuide(cloud,
2026-07-11 16:02:57 +08:00
m_configManager->GetAlgorithmParams(),
2026-07-14 17:14:55 +08:00
algorithmState,
result,
control);
2026-07-11 16:02:57 +08:00
result.cameraIndex = m_detectIndex;
if (ret != 0) {
result.errorCode = ret;
if (result.message.isEmpty()) {
result.message = QStringLiteral("停机引导检测失败");
2026-07-11 16:02:57 +08:00
}
} else {
if (result.parkingSpaceInfoList.empty() && result.message.isEmpty()) {
result.message = QStringLiteral("无停机引导结果");
2026-07-11 16:02:57 +08:00
}
2026-07-14 17:14:55 +08:00
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;
if (control.nextState.modelRecognitionAttempts >= 2) {
for (ParkingSpaceGuideInfo& info : result.parkingSpaceInfoList) {
info.hasException = true;
info.guideStateCode = static_cast<int>(
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().guideParam.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("机型识别结果未通过验证");
}
2026-07-14 17:14:55 +08:00
appendRecognitionFailure(reason);
}
}
} catch (const std::exception& e) {
appendRecognitionFailure(
QStringLiteral("机型识别异常:%1").arg(QString::fromUtf8(e.what())));
} catch (...) {
appendRecognitionFailure(QStringLiteral("机型识别发生未知异常"));
}
}
}
2026-07-11 16:02:57 +08:00
2026-07-14 17:14:55 +08:00
if (ret == 0) {
algorithmState = control.nextState;
}
PublishDetectionResult(result);
return ret == 0;
}
std::shared_ptr<ParkingSpaceGuidePresenter::OwnedCloudFrame>
ParkingSpaceGuidePresenter::AcquireCloudFrameBuffer()
{
std::lock_guard<std::mutex> 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<OwnedCloudFrame>& slot = m_cloudFrameSlots[slotIndex];
if (!slot) {
slot = std::make_shared<OwnedCloudFrame>();
}
if (slot.use_count() == 1) {
m_nextCloudFrameSlot = (slotIndex + 1) % m_cloudFrameSlots.size();
return slot;
}
}
return std::shared_ptr<OwnedCloudFrame>();
}
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<size_t>(line.nPointCount);
}
destination.points.resize(totalPointCount);
destination.cloud.clear();
destination.cloud.reserve(cloud.size());
destination.info = info;
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<size_t>(line.nPointCount));
line.p3DPoint = target;
pointOffset += static_cast<size_t>(line.nPointCount);
} else {
line.p3DPoint = nullptr;
}
destination.cloud.emplace_back(item.first, line);
}
return true;
}
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<int>(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)
{
EnrichDetectionResult(result);
if (StatusCallback()) {
StatusCallback()->OnDetectionResult(result);
}
2026-07-11 16:02:57 +08:00
UpdateCurrentStatus(result);
2026-07-14 17:14:55 +08:00
if (m_ledDisplay) {
m_ledDisplay->SendResult(ConvertLedResult(result));
}
BroadcastDetectionResult(result);
2026-07-14 17:14:55 +08:00
}
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_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<unsigned char[]> 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;
}
2026-07-11 16:02:57 +08:00
void ParkingSpaceGuidePresenter::DetectionLoop()
{
2026-07-14 17:14:55 +08:00
ParkingGuideAlgorithmState algorithmState;
while (!m_stopDetectionRequested.load()) {
2026-07-14 17:14:55 +08:00
std::shared_ptr<const OwnedCloudFrame> cloudFrame;
QString lidarError;
{
std::unique_lock<std::mutex> dataLock(m_dataMutex);
m_detectionCondition.wait(dataLock, [this]() {
return m_stopDetectionRequested.load() ||
m_lidarErrorPending ||
m_cloudSequence != m_consumedCloudSequence;
});
if (m_stopDetectionRequested.load()) {
break;
}
2026-07-14 17:14:55 +08:00
if (m_lidarErrorPending) {
lidarError = m_lidarErrorMessage;
m_lidarErrorPending = false;
} else {
cloudFrame = m_latestCloudFrame;
m_consumedCloudSequence = m_cloudSequence;
}
}
2026-07-14 17:14:55 +08:00
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;
}
try {
if (!m_trackingMode.load()) {
if (ProcessAirplanePresence(cloudFrame->cloud)) {
m_trackingMode.store(true);
}
continue;
}
DetectOnce(cloudFrame->cloud, algorithmState);
} catch (const std::exception& e) {
PublishErrorResult(-1,
QStringLiteral("停机引导检测异常:%1")
2026-07-14 17:14:55 +08:00
.arg(QString::fromUtf8(e.what())));
} catch (...) {
PublishErrorResult(-1, QStringLiteral("停机引导检测发生未知异常"));
2026-07-14 17:14:55 +08:00
}
2026-07-11 16:02:57 +08:00
}
m_detectionRunning.store(false);
2026-07-14 17:14:55 +08:00
m_trackingMode.store(false);
2026-07-11 16:02:57 +08:00
}
int ParkingSpaceGuidePresenter::StopDetection()
{
std::lock_guard<std::mutex> threadLock(m_detectionThreadMutex);
const bool wasRunning = m_detectionRunning.load();
2026-07-14 17:14:55 +08:00
{
std::lock_guard<std::mutex> dataLock(m_dataMutex);
m_stopDetectionRequested.store(true);
}
m_detectionCondition.notify_all();
if (m_detectionThread.joinable() &&
m_detectionThread.get_id() != std::this_thread::get_id()) {
m_detectionThread.join();
}
m_detectionRunning.store(false);
2026-07-14 17:14:55 +08:00
m_trackingMode.store(false);
m_lastProbeAcceptedMs.store(0);
if (wasRunning) {
NotifyWorkStatus(WorkStatus::Ready);
NotifyStatus("停机引导实时检测已停止");
}
2026-07-11 16:02:57 +08:00
return 0;
}
bool ParkingSpaceGuidePresenter::IsDetectionRunning() const
{
return m_detectionRunning.load();
}
2026-07-11 16:02:57 +08:00
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<std::mutex> 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";
2026-07-14 17:14:55 +08:00
file << "hasCloud=" << (m_latestCloudFrame ? 1 : 0) << "\n";
file << "cloudLines=" << (m_latestCloudFrame ? m_latestCloudFrame->cloud.size() : 0) << "\n";
2026-07-11 16:02:57 +08:00
return 0;
}
int ParkingSpaceGuidePresenter::GetDetectionDataCacheSize() const
{
std::lock_guard<std::mutex> lock(m_dataMutex);
2026-07-14 17:14:55 +08:00
return (m_hasFrame ? 1 : 0) + (m_latestCloudFrame ? 1 : 0);
2026-07-11 16:02:57 +08:00
}
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<std::pair<std::string, void*>> ParkingSpaceGuidePresenter::GetCameraList() const
{
return m_cameraList;
}
void ParkingSpaceGuidePresenter::OnConfigChanged(const ConfigResult& configResult)
{
std::lock_guard<std::mutex> reconfigurationLock(m_reconfigurationMutex);
2026-07-11 16:02:57 +08:00
if (!m_initialized) {
return;
}
m_reconfiguring.store(true);
const bool restartDetection = m_detectionRunning.load();
const int detectIndex = m_detectIndex;
StopDetection();
2026-07-11 16:02:57 +08:00
NotifyStatus("配置已变更,正在重新连接设备");
InitMvsCamera(configResult.mvsCamera);
InitRsLidar(configResult.lidarConfig);
InitLedDisplay(configResult.ledDisplayConfig);
InitUdpBroadcast(configResult.udpBroadcastConfig);
InitHttpServer(configResult.httpServerConfig);
m_reconfiguring.store(false);
if (restartDetection) {
StartDetection(detectIndex);
}
2026-07-11 16:02:57 +08:00
}
IYParkingSpaceGuideStatus* ParkingSpaceGuidePresenter::StatusCallback() const
{
return static_cast<IYParkingSpaceGuideStatus*>(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::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<std::mutex> 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<void*>(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<MvsDeviceInfo> 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<unsigned int>((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));
}
2026-07-14 17:14:55 +08:00
ret = m_mvsDevice->SetTriggerMode(true);
2026-07-11 16:02:57 +08:00
if (ret != 0) {
2026-07-14 17:14:55 +08:00
NotifyCameraStatus(false);
NotifyStatus("启用机型识别相机触发模式失败:" + std::to_string(ret));
closeMvsDevice();
return ret;
2026-07-11 16:02:57 +08:00
}
2026-07-14 17:14:55 +08:00
ret = m_mvsDevice->SetEnumFeatureByString("TriggerSource", "Software");
2026-07-11 16:02:57 +08:00
if (ret != 0) {
NotifyCameraStatus(false);
2026-07-14 17:14:55 +08:00
NotifyStatus("设置机型识别相机软触发源失败:" + std::to_string(ret));
2026-07-11 16:02:57 +08:00
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<void*>(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;
}
2026-07-14 17:14:55 +08:00
{
std::lock_guard<std::mutex> dataLock(m_dataMutex);
m_latestCloudFrame.reset();
for (std::shared_ptr<OwnedCloudFrame>& slot : m_cloudFrameSlots) {
slot.reset();
}
m_nextCloudFrameSlot = 0;
m_cloudSequence = 0;
m_consumedCloudSequence = 0;
m_lidarErrorPending = false;
m_lidarErrorMessage.clear();
}
m_trackingMode.store(false);
m_lastProbeAcceptedMs.store(0);
m_cloudCopyErrorReported.store(false);
2026-07-11 16:02:57 +08:00
if (IRsLidarDevice::CreateObject(&m_lidarDevice) != 0 || !m_lidarDevice) {
NotifyLidarStatus(false);
return -1;
}
m_lidarDevice->SetPointCloudCallback([this](const RsCloudData& cloud, const RsFrameInfo& info) {
2026-07-14 17:14:55 +08:00
if (!m_trackingMode.load()) {
const long long nowMs = std::chrono::duration_cast<std::chrono::milliseconds>(
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);
}
try {
std::shared_ptr<OwnedCloudFrame> frame = AcquireCloudFrameBuffer();
if (!frame) {
return;
}
if (!CopyCloudFrame(cloud, info, *frame)) {
if (!m_cloudCopyErrorReported.exchange(true)) {
NotifyStatus("雷达点云格式非 PointXYZI已丢弃当前帧");
}
return;
}
m_cloudCopyErrorReported.store(false);
{
std::lock_guard<std::mutex> dataLock(m_dataMutex);
m_latestCloudFrame = frame;
++m_cloudSequence;
}
m_detectionCondition.notify_one();
} catch (const std::exception& e) {
if (!m_cloudCopyErrorReported.exchange(true)) {
NotifyStatus(std::string("复制雷达点云失败:") + e.what());
}
} catch (...) {
if (!m_cloudCopyErrorReported.exchange(true)) {
NotifyStatus("复制雷达点云发生未知异常");
}
}
2026-07-11 16:02:57 +08:00
});
m_lidarDevice->SetExceptionCallback([this](const RsExceptionInfo& info) {
2026-07-14 17:14:55 +08:00
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<std::mutex> dataLock(m_dataMutex);
m_lidarErrorPending = true;
m_lidarErrorMessage = message;
}
if (fatal) {
m_detectionCondition.notify_all();
}
NotifyStatus(message.toUtf8().toStdString());
if (fatal) {
NotifyLidarStatus(false);
}
2026-07-11 16:02:57 +08:00
});
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<ParkingSpaceGuidePresenter*>(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<std::mutex> 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<std::mutex> 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;
}
}
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<std::mutex> lock(m_statusMutex);
m_currentStatusJson.assign(data.constData(), static_cast<size_t>(data.size()));
}
std::string ParkingSpaceGuidePresenter::CurrentStatusJson() const
{
std::lock_guard<std::mutex> 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<std::mutex> lock(m_udpMutex);
if (!m_udpClient || !m_udpBroadcastConfig.enabled) {
return false;
}
const std::string currentStatus = CurrentStatusJson();
const QByteArray data(currentStatus.data(), static_cast<int>(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;
2026-07-14 17:14:55 +08:00
if (result.errorCode != 0) {
output.valid = true;
output.guideCode = 2;
return output;
}
output.valid = !result.parkingSpaceInfoList.empty();
2026-07-11 16:02:57 +08:00
if (!output.valid) {
return output;
}
const ParkingSpaceGuideInfo& info = result.parkingSpaceInfoList.front();
output.guideCode = (info.hasException || IsExceptionGuideState(info.guideStateCode)) ? 2 : 1;
output.distance = static_cast<float>(info.distance);
output.lateralOffset = static_cast<float>(info.lateralOffset);
output.angle = static_cast<float>(info.angle);
return output;
}
2026-07-14 17:14:55 +08:00
bool ParkingSpaceGuidePresenter::CacheMvsFrame(const MvsImageData& image, QImage& frame)
2026-07-11 16:02:57 +08:00
{
2026-07-14 17:14:55 +08:00
frame = QImage();
2026-07-11 16:02:57 +08:00
if (!image.pData || image.width == 0 || image.height == 0 || image.dataSize == 0) {
2026-07-14 17:14:55 +08:00
return false;
2026-07-11 16:02:57 +08:00
}
const int width = static_cast<int>(image.width);
const int height = static_cast<int>(image.height);
const size_t pixelCount = static_cast<size_t>(width) * static_cast<size_t>(height);
if (pixelCount == 0) {
2026-07-14 17:14:55 +08:00
return false;
2026-07-11 16:02:57 +08:00
}
QImage rgbImage;
2026-07-14 17:14:55 +08:00
if (image.pixelFormat == kMvsPixelTypeRGB8 && image.dataSize >= pixelCount * 3) {
2026-07-11 16:02:57 +08:00
QImage src(image.pData, width, height, width * 3, QImage::Format_RGB888);
2026-07-14 17:14:55 +08:00
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) {
2026-07-11 16:02:57 +08:00
QImage src(image.pData, width, height, width, QImage::Format_Grayscale8);
rgbImage = src.convertToFormat(QImage::Format_RGB888);
} else {
2026-07-14 17:14:55 +08:00
return false;
2026-07-11 16:02:57 +08:00
}
if (rgbImage.isNull()) {
2026-07-14 17:14:55 +08:00
return false;
2026-07-11 16:02:57 +08:00
}
std::vector<unsigned char> compact(pixelCount * 3);
for (int row = 0; row < height; ++row) {
std::memcpy(compact.data() + static_cast<size_t>(row) * static_cast<size_t>(width) * 3,
rgbImage.constScanLine(row),
static_cast<size_t>(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<std::mutex> lock(m_dataMutex);
m_latestFrame.swap(compact);
m_latestFrameInfo = frameInfo;
m_hasFrame = true;
2026-07-14 17:14:55 +08:00
frame = rgbImage;
return true;
2026-07-11 16:02:57 +08:00
}