#include "TunnelChannelPresenter.h" #include "VrLog.h" #include "VrTimeUtils.h" #include "VrError.h" #include "PointCloudImageUtils.h" #include #include #include TunnelChannelPresenter::TunnelChannelPresenter(QObject *parent) : BasePresenter(parent) , m_pConfigManager(nullptr) , m_pHikDevice(nullptr) , m_bHikConnected(false) , m_pHikReconnectTimer(nullptr) , m_pDetectPresenter(nullptr) { // 连接跨线程信号槽(使用QueuedConnection确保在主线程执行) connect(this, &TunnelChannelPresenter::sigHikImageUpdated, this, &TunnelChannelPresenter::onHikImageUpdatedInMainThread, Qt::QueuedConnection); connect(this, &TunnelChannelPresenter::sigHikStatusChanged, this, &TunnelChannelPresenter::onHikStatusChangedInMainThread, Qt::QueuedConnection); connect(this, &TunnelChannelPresenter::sigHikException, this, &TunnelChannelPresenter::onHikExceptionInMainThread, Qt::QueuedConnection); LOG_INFO("TunnelChannelPresenter created\n"); } TunnelChannelPresenter::~TunnelChannelPresenter() { // 停止海康预览并清理设备 if (m_pHikDevice) { m_pHikDevice->StopPreview(); m_pHikDevice->Logout(); m_pHikDevice->CleanupSDK(); delete m_pHikDevice; m_pHikDevice = nullptr; } // 停止海康重连定时器 if (m_pHikReconnectTimer) { m_pHikReconnectTimer->stop(); delete m_pHikReconnectTimer; m_pHikReconnectTimer = nullptr; } // 清理配置管理器 if (m_pConfigManager) { delete m_pConfigManager; m_pConfigManager = nullptr; } // 清理检测处理器 if (m_pDetectPresenter) { delete m_pDetectPresenter; m_pDetectPresenter = nullptr; } LOG_INFO("TunnelChannelPresenter destroyed\n"); } int TunnelChannelPresenter::InitApp() { LOG_INFO("TunnelChannelPresenter::InitApp() - Starting initialization\n"); // 1. 初始化配置管理器 m_pConfigManager = new ConfigManager(); if (!m_pConfigManager->Initialize()) { LOG_ERR("Failed to initialize ConfigManager\n"); return ERR_CODE(DEV_OPEN_ERR); } // 设置配置变化通知 m_pConfigManager->SetConfigChangeNotify(this); // 2. 初始化海康设备 int hikResult = InitHikDevice(); if (hikResult != 0) { LOG_WARN("Failed to initialize HikDevice, error: %d. Hik camera will not be available.\n", hikResult); // 海康SDK初始化失败不阻止程序运行 } // 3. 初始化3D相机(使用BasePresenter的InitCamera) ConfigResult config = m_pConfigManager->GetCurrentConfig(); std::vector cameraList = config.cameraList; // 如果没有配置相机,添加一个空配置让设备自动搜索第一个相机 if (cameraList.empty()) { DeviceInfo autoSearchCamera; autoSearchCamera.name = "相机"; autoSearchCamera.ip = ""; // 空IP会触发自动搜索 cameraList.push_back(autoSearchCamera); LOG_INFO("No camera configured, will auto search first available camera\n"); } // 不需要RGB,需要摆动 int cameraResult = InitCamera(cameraList, false, true); if (cameraResult != 0) { LOG_WARN("Failed to initialize 3D cameras, error: %d\n", cameraResult); } // 通知相机数量 OnCameraCountChanged(static_cast(cameraList.size())); // 4. 初始化海康相机 if (m_pHikDevice) { HikCameraConfig hikConfig; bool hasHikConfig = false; if (!config.hikCameraList.empty() && !config.hikCameraList[0].ip.empty()) { // 使用配置文件中的海康相机配置 hikConfig = config.hikCameraList[0]; hasHikConfig = hikConfig.enabled; LOG_INFO("Using configured Hik camera: %s\n", hikConfig.ip.c_str()); } else { // 没有配置IP,提示用户配置 LOG_WARN("No Hik camera IP configured\n"); OnStatusUpdate("未配置2D相机IP"); } int connectResult = ConnectHikCamera(hikConfig); if (connectResult == 0) { m_bHikConnected = true; NotifyHikCameraStatus(true); LOG_INFO("Hik camera connected successfully\n"); // 关闭OSD显示(通道名称和时间) int osdResult = m_pHikDevice->ConfigureOSD(false, false); if (osdResult != 0) { LOG_WARN("Failed to configure OSD, error: %d\n", osdResult); } // 开始实时预览(根据是否设置窗口句柄选择渲染方式) if (m_hHikDisplayWnd) { StartHikPreviewEx(m_hHikDisplayWnd); // 硬件渲染,低延迟 } else { StartHikPreview(); // 软件解码回调 } } else { LOG_WARN("Failed to connect Hik camera, will retry later\n"); NotifyHikCameraStatus(false); m_bHikConnected = false; } } // 5. 创建海康相机重连定时器 m_pHikReconnectTimer = new QTimer(this); m_pHikReconnectTimer->setInterval(5000); // 5秒重试一次 connect(m_pHikReconnectTimer, &QTimer::timeout, this, &TunnelChannelPresenter::OnHikReconnectTimer); // 如果海康相机未连接,启动重连定时器 if (m_pHikDevice && !m_bHikConnected) { m_pHikReconnectTimer->start(); LOG_INFO("Hik camera reconnect timer started (interval: 5000ms)\n"); } // 6. 更新工作状态 CheckAndUpdateWorkStatus(); LOG_INFO("TunnelChannelPresenter::InitApp() - Initialization completed\n"); return SUCCESS; } int TunnelChannelPresenter::InitAlgoParams() { LOG_INFO("TunnelChannelPresenter::InitAlgoParams()\n"); // 算法暂时留空 // TODO: 加载隧道检测算法参数 return SUCCESS; } int TunnelChannelPresenter::ProcessAlgoDetection( std::vector>& detectionDataCache) { LOG_INFO("TunnelChannelPresenter::ProcessAlgoDetection() - Processing %zu data items\n", detectionDataCache.size()); if (detectionDataCache.empty()) { LOG_WARN("Detection data cache is empty\n"); return ERR_CODE(DEV_RESULT_EMPTY); } // 创建检测结果 TunnelDetectionResult result; result.cameraIndex = m_currentCameraIndex; result.errorCode = 0; // 0表示成功 // ========== 算法处理(暂时留空) ========== // TODO: 实现隧道通道检测算法 // 1. 从点云数据中提取特征 // 2. 进行隧道轮廓检测 // 3. 计算检测结果 // 暂时使用点云生成深度图作为检测结果 QImage depthImage; int imageResult = PointCloudImageUtils::GenerateDepthImage(detectionDataCache, depthImage); if (imageResult == 0 && !depthImage.isNull()) { result.image = depthImage; } else { LOG_WARN("Failed to generate depth image\n"); } // 获取海康相机当前帧 result.hikImage = GetCurrentHikFrame(); // 通知UI更新检测结果 auto* pStatusCallback = GetStatusCallback(); if (pStatusCallback) { pStatusCallback->OnDetectionResult(result); } LOG_INFO("TunnelChannelPresenter::ProcessAlgoDetection() - Detection completed\n"); return SUCCESS; } void TunnelChannelPresenter::OnCameraStatusChanged(int cameraIndex, bool isConnected) { LOG_INFO("TunnelChannelPresenter::OnCameraStatusChanged() - Camera %d: %s\n", cameraIndex, isConnected ? "Connected" : "Disconnected"); auto* pStatusCallback = GetStatusCallback(); if (pStatusCallback) { if (cameraIndex == 1) { pStatusCallback->OnCamera1StatusChanged(isConnected); } else if (cameraIndex == 2) { pStatusCallback->OnCamera2StatusChanged(isConnected); } } CheckAndUpdateWorkStatus(); } void TunnelChannelPresenter::OnWorkStatusChanged(WorkStatus status) { LOG_INFO("TunnelChannelPresenter::OnWorkStatusChanged() - Status: %s\n", WorkStatusToString(status).c_str()); auto* pStatusCallback = GetStatusCallback(); if (pStatusCallback) { pStatusCallback->OnWorkStatusChanged(status); } } void TunnelChannelPresenter::OnCameraCountChanged(int count) { LOG_INFO("TunnelChannelPresenter::OnCameraCountChanged() - Count: %d\n", count); } void TunnelChannelPresenter::OnStatusUpdate(const std::string& statusMessage) { auto* pStatusCallback = GetStatusCallback(); if (pStatusCallback) { pStatusCallback->OnStatusUpdate(statusMessage); } } void TunnelChannelPresenter::OnConfigChanged(const ConfigResult& configResult) { LOG_INFO("TunnelChannelPresenter::OnConfigChanged()\n"); // 配置变化处理 } // ============ 海康相机相关实现 ============ int TunnelChannelPresenter::InitHikDevice() { LOG_INFO("TunnelChannelPresenter::InitHikDevice()\n"); // 创建海康设备对象 int result = IHikDevice::CreateObject(&m_pHikDevice); if (result != 0 || !m_pHikDevice) { LOG_ERR("Failed to create HikDevice object\n"); return ERR_CODE(DATA_ERR_MEM); } // 初始化海康SDK result = m_pHikDevice->InitSDK(); if (result != 0) { LOG_ERR("Failed to initialize HikDevice SDK, error: %d\n", result); delete m_pHikDevice; m_pHikDevice = nullptr; return result; } // 设置帧数据回调 m_pHikDevice->SetDecodedFrameCallback( [this](unsigned char* pRGBData, int dataSize, const HikFrameInfo& frameInfo, void* pUser) { this->OnHikFrameReceived(pRGBData, dataSize, frameInfo); }, this ); // 设置状态回调 m_pHikDevice->SetStatusCallback( [this](EHikDeviceStatus status, void* pUser) { this->OnHikDeviceStatusChanged(status); }, this ); // 设置异常回调 m_pHikDevice->SetExceptionCallback( [this](EHikExceptionType exceptionType, void* pUser) { this->OnHikExceptionReceived(exceptionType); }, this ); LOG_INFO("TunnelChannelPresenter::InitHikDevice() - HikDevice initialized successfully\n"); return SUCCESS; } int TunnelChannelPresenter::ConnectHikCamera(const HikCameraConfig& config) { LOG_INFO("TunnelChannelPresenter::ConnectHikCamera() - IP: %s, Port: %d\n", config.ip.c_str(), config.port); if (!m_pHikDevice) { LOG_ERR("HikDevice not initialized\n"); return ERR_CODE(DEV_NO_OPEN); } // 转换配置 HikDeviceConfig deviceConfig; deviceConfig.ip = config.ip; deviceConfig.port = config.port; deviceConfig.username = config.username; deviceConfig.password = config.password; deviceConfig.channelNo = config.channelNo; deviceConfig.streamType = config.streamType; deviceConfig.enabled = config.enabled; // 登录相机 int result = m_pHikDevice->Login(deviceConfig); if (result != 0) { if (result == ERR_CODE(DEV_UNACTIVATE)) { LOG_ERR("2D相机未激活,请先激活设备后再连接 (IP: %s)\n", config.ip.c_str()); OnStatusUpdate("2D相机未激活,请先激活设备"); } else { LOG_ERR("Failed to login Hik camera, error: %d \n", result); } return result; } m_bHikConnected = true; LOG_INFO("TunnelChannelPresenter::ConnectHikCamera() - Connected successfully\n"); return SUCCESS; } void TunnelChannelPresenter::DisconnectHikCamera() { if (m_pHikDevice) { m_pHikDevice->StopPreview(); m_pHikDevice->Logout(); m_bHikConnected = false; LOG_INFO("TunnelChannelPresenter::DisconnectHikCamera() - Disconnected\n"); } } int TunnelChannelPresenter::StartHikPreview() { LOG_INFO("TunnelChannelPresenter::StartHikPreview()\n"); if (!m_pHikDevice) { LOG_ERR("HikDevice not initialized\n"); return ERR_CODE(DEV_NO_OPEN); } if (!m_pHikDevice->IsLoggedIn()) { LOG_ERR("Hik camera not logged in\n"); return ERR_CODE(DEV_NO_OPEN); } int result = m_pHikDevice->StartPreview(); if (result != 0) { LOG_ERR("Failed to start Hik preview, error: %d\n", result); return result; } LOG_INFO("TunnelChannelPresenter::StartHikPreview() - Preview started\n"); return SUCCESS; } int TunnelChannelPresenter::StartHikPreviewEx(void* hWnd) { LOG_INFO("TunnelChannelPresenter::StartHikPreviewEx() - hWnd: %p\n", hWnd); if (!m_pHikDevice) { LOG_ERR("HikDevice not initialized\n"); return ERR_CODE(DEV_NO_OPEN); } if (!m_pHikDevice->IsLoggedIn()) { LOG_ERR("Hik camera not logged in\n"); return ERR_CODE(DEV_NO_OPEN); } int result = m_pHikDevice->StartPreviewEx(hWnd); if (result != 0) { LOG_ERR("Failed to start Hik preview (hardware), error: %d\n", result); return result; } LOG_INFO("TunnelChannelPresenter::StartHikPreviewEx() - Hardware preview started\n"); return SUCCESS; } void TunnelChannelPresenter::StopHikPreview() { if (m_pHikDevice) { m_pHikDevice->StopPreview(); LOG_INFO("TunnelChannelPresenter::StopHikPreview() - Preview stopped\n"); } } QImage TunnelChannelPresenter::GetCurrentHikFrame() const { std::lock_guard lock(m_hikFrameMutex); return m_currentHikFrame; } bool TunnelChannelPresenter::IsHikCameraConnected() const { return m_bHikConnected && m_pHikDevice && m_pHikDevice->IsLoggedIn(); } // ============ 海康相机回调处理 ============ void TunnelChannelPresenter::OnHikFrameReceived(unsigned char* pRGBData, int dataSize, const HikFrameInfo& frameInfo) { if (!pRGBData || dataSize <= 0) { return; } // 帧率限制:避免过度刷新UI auto now = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast(now - m_lastHikFrameTime).count(); if (elapsed < HIK_FRAME_INTERVAL_MS) { return; // 跳过此帧 } m_lastHikFrameTime = now; int width = frameInfo.width; int height = frameInfo.height; int bytesPerLine = width * 4; // RGB32 格式: 每像素4字节 // 优化: 使用外部数据构造临时QImage,然后一次性拷贝 // 避免多次memcpy,只进行一次深拷贝 QImage tempImage(pRGBData, width, height, bytesPerLine, QImage::Format_RGB32); QImage image = tempImage.copy(); // 只进行一次深拷贝 // 更新当前帧(使用移动语义,避免不必要的拷贝) { std::lock_guard lock(m_hikFrameMutex); m_currentHikFrame = std::move(image); } // 通过信号通知UI更新(QImage隐式共享,不会实际拷贝) emit sigHikImageUpdated(m_currentHikFrame); } void TunnelChannelPresenter::OnHikDeviceStatusChanged(EHikDeviceStatus status) { LOG_INFO("TunnelChannelPresenter::OnHikDeviceStatusChanged() - Status: %d\n", static_cast(status)); // 通过信号转发到主线程处理(跨线程安全) emit sigHikStatusChanged(static_cast(status)); } void TunnelChannelPresenter::OnHikExceptionReceived(EHikExceptionType exceptionType) { LOG_WARN("TunnelChannelPresenter::OnHikExceptionReceived() - Exception: %d\n", static_cast(exceptionType)); // 通过信号转发到主线程处理(跨线程安全) emit sigHikException(static_cast(exceptionType)); } void TunnelChannelPresenter::OnHikReconnectTimer() { if (m_bHikConnected) { return; // 已连接,不需要重连 } LOG_INFO("TunnelChannelPresenter::OnHikReconnectTimer() - Attempting to reconnect Hik camera\n"); ConfigResult config = m_pConfigManager->GetCurrentConfig(); if (config.hikCameraList.empty()) { return; } const HikCameraConfig& hikConfig = config.hikCameraList[0]; if (!hikConfig.enabled) { return; } // 尝试重新连接 int result = ConnectHikCamera(hikConfig); if (result == 0) { m_bHikConnected = true; NotifyHikCameraStatus(true); m_pHikReconnectTimer->stop(); // 关闭OSD显示(通道名称和时间) m_pHikDevice->ConfigureOSD(false, false); // 重新开始预览(根据是否设置窗口句柄选择渲染方式) if (m_hHikDisplayWnd) { StartHikPreviewEx(m_hHikDisplayWnd); } else { StartHikPreview(); } } } void TunnelChannelPresenter::CheckAndUpdateWorkStatus() { bool hasCameraConnected = m_bCameraConnected || m_bHikConnected; if (hasCameraConnected) { SetWorkStatus(WorkStatus::Ready); } else { // 如果没有任何相机连接,保持初始化状态 if (GetCurrentWorkStatus() != WorkStatus::InitIng) { SetWorkStatus(WorkStatus::Ready); } } } void TunnelChannelPresenter::NotifyHikCameraStatus(bool isConnected) { // 2D海康相机使用相机2的状态更新接口 auto* pStatusCallback = GetStatusCallback(); if (pStatusCallback) { pStatusCallback->OnCamera2StatusChanged(isConnected); } } void TunnelChannelPresenter::NotifyRobotConnectionStatus(bool isConnected) { auto* pStatusCallback = GetStatusCallback(); if (pStatusCallback) { pStatusCallback->OnRobotConnectionChanged(isConnected); } } void TunnelChannelPresenter::NotifySerialConnectionStatus(bool isConnected) { auto* pStatusCallback = GetStatusCallback(); if (pStatusCallback) { pStatusCallback->OnSerialConnectionChanged(isConnected); } } // ============ 相机调平接口实现 ============ bool TunnelChannelPresenter::CalculatePlaneCalibration( const std::vector>& scanData, double planeCalib[9], double& planeHeight, double invRMatrix[9]) { // TODO: 实现平面调平计算 // 暂时返回单位矩阵 for (int i = 0; i < 9; i++) { planeCalib[i] = (i % 4 == 0) ? 1.0 : 0.0; invRMatrix[i] = (i % 4 == 0) ? 1.0 : 0.0; } planeHeight = 0.0; return true; } bool TunnelChannelPresenter::SaveLevelingResults( double planeCalib[9], double planeHeight, double invRMatrix[9], int cameraIndex, const QString& cameraName) { // TODO: 保存调平结果到配置文件 LOG_INFO("SaveLevelingResults for camera %d (%s)\n", cameraIndex, cameraName.toStdString().c_str()); return true; } bool TunnelChannelPresenter::LoadLevelingResults( int cameraIndex, const QString& cameraName, double planeCalib[9], double& planeHeight, double invRMatrix[9]) { // TODO: 从配置文件加载调平结果 LOG_INFO("LoadLevelingResults for camera %d (%s)\n", cameraIndex, cameraName.toStdString().c_str()); // 默认返回单位矩阵 for (int i = 0; i < 9; i++) { planeCalib[i] = (i % 4 == 0) ? 1.0 : 0.0; invRMatrix[i] = (i % 4 == 0) ? 1.0 : 0.0; } planeHeight = 0.0; return true; } // ============ 跨线程槽函数实现(在主线程执行) ============ void TunnelChannelPresenter::onHikImageUpdatedInMainThread(const QImage& image) { // 通知UI更新(在主线程中安全调用) auto* pStatusCallback = GetStatusCallback(); if (pStatusCallback) { pStatusCallback->OnHikImageUpdated(image); } } void TunnelChannelPresenter::onHikStatusChangedInMainThread(int status) { EHikDeviceStatus hikStatus = static_cast(status); switch (hikStatus) { case EHikDeviceStatus::Connected: m_bHikConnected = true; NotifyHikCameraStatus(true); break; case EHikDeviceStatus::Disconnected: case EHikDeviceStatus::Error: m_bHikConnected = false; NotifyHikCameraStatus(false); // 启动重连定时器(在主线程中直接操作QTimer) if (m_pHikReconnectTimer && !m_pHikReconnectTimer->isActive()) { m_pHikReconnectTimer->start(); LOG_INFO("Hik camera reconnect timer started due to status change\n"); } break; case EHikDeviceStatus::Reconnecting: LOG_INFO("Hik camera is reconnecting...\n"); break; case EHikDeviceStatus::Previewing: LOG_INFO("Hik camera is previewing\n"); break; } CheckAndUpdateWorkStatus(); } void TunnelChannelPresenter::onHikExceptionInMainThread(int exceptionType) { EHikExceptionType hikException = static_cast(exceptionType); switch (hikException) { case EHikExceptionType::PreviewException: // 预览异常,停止预览 StopHikPreview(); m_bHikConnected = false; NotifyHikCameraStatus(false); // 启动重连定时器(在主线程中直接操作QTimer) if (m_pHikReconnectTimer && !m_pHikReconnectTimer->isActive()) { m_pHikReconnectTimer->start(); LOG_INFO("Hik camera reconnect timer started due to exception\n"); } break; case EHikExceptionType::Reconnect: LOG_INFO("Hik camera preview reconnecting...\n"); break; default: break; } }