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

678 lines
22 KiB
C++
Raw Normal View History

#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;
}
}