GrabBag/App/TunnelChannel/TunnelChannelApp/Presenter/Src/TunnelChannelPresenter.cpp

678 lines
22 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 "TunnelChannelPresenter.h"
#include "VrLog.h"
#include "VrTimeUtils.h"
#include "VrError.h"
#include "PointCloudImageUtils.h"
#include <QCoreApplication>
#include <QDir>
#include <algorithm>
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<DeviceInfo> 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<int>(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<std::pair<EVzResultDataType, SVzLaserLineData>>& 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<IYTunnelChannelStatus>();
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<IYTunnelChannelStatus>();
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<IYTunnelChannelStatus>();
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<IYTunnelChannelStatus>();
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<std::mutex> 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<std::chrono::milliseconds>(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<std::mutex> 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<int>(status));
// 通过信号转发到主线程处理(跨线程安全)
emit sigHikStatusChanged(static_cast<int>(status));
}
void TunnelChannelPresenter::OnHikExceptionReceived(EHikExceptionType exceptionType)
{
LOG_WARN("TunnelChannelPresenter::OnHikExceptionReceived() - Exception: %d\n",
static_cast<int>(exceptionType));
// 通过信号转发到主线程处理(跨线程安全)
emit sigHikException(static_cast<int>(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<IYTunnelChannelStatus>();
if (pStatusCallback) {
pStatusCallback->OnCamera2StatusChanged(isConnected);
}
}
void TunnelChannelPresenter::NotifyRobotConnectionStatus(bool isConnected)
{
auto* pStatusCallback = GetStatusCallback<IYTunnelChannelStatus>();
if (pStatusCallback) {
pStatusCallback->OnRobotConnectionChanged(isConnected);
}
}
void TunnelChannelPresenter::NotifySerialConnectionStatus(bool isConnected)
{
auto* pStatusCallback = GetStatusCallback<IYTunnelChannelStatus>();
if (pStatusCallback) {
pStatusCallback->OnSerialConnectionChanged(isConnected);
}
}
// ============ 相机调平接口实现 ============
bool TunnelChannelPresenter::CalculatePlaneCalibration(
const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& 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<IYTunnelChannelStatus>();
if (pStatusCallback) {
pStatusCallback->OnHikImageUpdated(image);
}
}
void TunnelChannelPresenter::onHikStatusChangedInMainThread(int status)
{
EHikDeviceStatus hikStatus = static_cast<EHikDeviceStatus>(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<EHikExceptionType>(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;
}
}