2009 lines
69 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "ParkingSpaceGuidePresenter.h"
#include <algorithm>
#include <chrono>
#include <cstring>
#include <exception>
#include <fstream>
#include <limits>
#include <QCollator>
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QImageReader>
#include <QJsonDocument>
#include <QJsonObject>
#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<QString>& 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::milliseconds>(
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<int>(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<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);
}
if (StatusCallback()) {
StatusCallback()->OnDetectionResult(waitingDisplay);
}
m_initialized = true;
NotifyWorkStatus(WorkStatus::Ready);
NotifyStatus("停机引导初始化完成");
return 0;
}
void ParkingSpaceGuidePresenter::DeinitApp()
{
m_initialized.store(false);
{
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<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;
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<std::mutex> 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<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()
.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::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;
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<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::SubmitCloudFrame(const RsCloudData& cloud,
const RsFrameInfo& info)
{
try {
std::shared_ptr<OwnedCloudFrame> 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<OwnedCloudFrame>& frame)
{
if (!frame ||
(m_simulationOptions.enabled && m_stopDetectionRequested.load())) {
return 0;
}
unsigned long long sequence = 0;
{
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<unsigned long long>(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<OwnedCloudFrame>& radarFrame =
m_simulationRadarFrames[frameIndex];
radarFrame->timestampMs = static_cast<qint64>(frameIndex) *
static_cast<qint64>(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::milliseconds>(
std::chrono::steady_clock::now() - frameStartedAt)
.count();
const int remainingMs = elapsedMs >=
static_cast<long long>(m_simulationOptions.frameIntervalMs)
? 0
: m_simulationOptions.frameIntervalMs -
static_cast<int>(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<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)
{
if (m_simulationOptions.enabled && result.image.isNull()) {
std::lock_guard<std::mutex> 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<std::mutex> 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<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;
}
void ParkingSpaceGuidePresenter::DetectionLoop()
{
ParkingGuideAlgorithmState algorithmState;
unsigned long long activeSimulationRound = 0;
while (!m_stopDetectionRequested.load()) {
std::shared_ptr<const OwnedCloudFrame> cloudFrame;
unsigned long long frameSequence = 0;
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;
}
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<std::mutex> 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<std::mutex> threadLock(m_detectionThreadMutex);
const bool wasRunning = m_detectionRunning.load();
{
std::lock_guard<std::mutex> 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<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";
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<std::mutex> 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<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);
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<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::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<OwnedCloudFrame> 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<std::mutex> 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<void*>(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<std::mutex> dataLock(m_dataMutex);
m_simulationCameraImage = QImage();
m_latestFrame.clear();
m_latestFrameInfo = CameraFrameInfo();
m_hasFrame = false;
m_latestCloudFrame.reset();
for (std::shared_ptr<OwnedCloudFrame>& 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<OwnedCloudFrame>& 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<std::pair<EVzResultDataType, SVzLaserLineData>> 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<int>(item.first));
loader.FreeLaserScanData(loadedData);
return false;
}
const size_t pointCount = static_cast<size_t>(line.nPointCount);
if (pointCount > (std::numeric_limits<size_t>::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<OwnedCloudFrame> converted =
std::make_shared<OwnedCloudFrame>();
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<size_t>(pointIndex)];
if (item.first == keResultDataType_Position) {
const auto* sourcePoints =
static_cast<const SVzNL3DPosition*>(sourceLine.p3DPoint);
targetPoint.x = static_cast<float>(sourcePoints[pointIndex].pt3D.x);
targetPoint.y = static_cast<float>(sourcePoints[pointIndex].pt3D.y);
targetPoint.z = static_cast<float>(sourcePoints[pointIndex].pt3D.z);
targetPoint.intensity = 0.0f;
} else if (item.first == keResultDataType_PointXYZRGBA) {
const auto* sourcePoints =
static_cast<const SVzNLPointXYZRGBA*>(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<const SVzNLPointXYZI*>(sourceLine.p3DPoint);
targetPoint = sourcePoints[pointIndex];
}
}
converted->cloud.emplace_back(keResultDataType_PointXYZI,
targetLine);
pointOffset += static_cast<size_t>(sourceLine.nPointCount);
}
converted->info.height = static_cast<uint32_t>(loadedData.size());
converted->info.width = static_cast<uint32_t>(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<unsigned long long>(frameIndex + 1);
frameInfo.timestamp = static_cast<unsigned long long>(
QDateTime::currentMSecsSinceEpoch());
std::lock_guard<std::mutex> 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<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));
}
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<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;
}
{
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_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::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);
}
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<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);
}
});
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;
}
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<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;
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<float>(info.distance);
output.lateralOffset = static_cast<float>(info.lateralOffset);
output.angle = static_cast<float>(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<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) {
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<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;
frame = rgbImage;
return true;
}