GrabBag/AppUtils/AppCommon/Inc/BasePresenter.h

753 lines
24 KiB
C
Raw Normal View History

2026-03-28 10:49:55 +08:00
#ifndef BASEPRESENTER_H
#define BASEPRESENTER_H
#include <atomic>
#include <condition_variable>
#include <thread>
#include <mutex>
#include <vector>
2026-05-17 00:38:31 +08:00
#include <queue>
#include <map>
#include <set>
2026-03-28 10:49:55 +08:00
#include <memory>
#include <string>
#include <QObject>
#include <QTimer>
#include <QImage>
2026-03-28 10:49:55 +08:00
#include "IVrEyeDevice.h"
#include "VZNL_Types.h"
#include "VrCommonConfig.h" // 包含公共配置结构体
#include "LaserDataLoader.h" // 激光数据加载器
#include "IVisionApplicationStatus.h" // 工作状态枚举
#include "DebugDataSaver.h" // 调试数据异步存储
2026-03-28 10:49:55 +08:00
// 前向声明
class IYModbusTCPServer;
2026-05-17 00:38:31 +08:00
class BasePresenter;
/**
* @brief 相机回调上下文
*
* 携带 BasePresenter 指针和相机索引,解决多相机同时扫描时
* 静态回调函数中无法识别数据来源相机的问题
*/
struct CameraCallbackContext
{
BasePresenter* presenter;
int cameraIndex;
};
2026-03-28 10:49:55 +08:00
/**
* @brief 基础Presenter类
*
* 提取各应用Presenter的共同功能:
* - 相机设备管理(InitCamera)
* - 算法参数初始化(InitAlgoParams - 纯虚函数)
* - 检测线程管理(DetectTask)
* - 检测数据缓存
* - 相机重连机制
*
* 子类需要实现的纯虚函数:
* - InitAlgoParams(): 初始化算法参数
* - ProcessAlgoDetection(): 执行具体的算法检测
* - OpenDevice(): 打开相机设备
* - TryReconnectCameras(): 尝试重连相机
* - OnCameraStatusChanged(): 相机状态变化通知
*
* 用法示例:
* @code
* class GrabBagPresenter : public BasePresenter {
* protected:
* int InitAlgoParams() override { ... }
* int ProcessAlgoDetection() override { ... }
* int OpenDevice(int cameraIndex, ...) override { ... }
* };
* @endcode
*/
class BasePresenter : public QObject
{
Q_OBJECT
public:
explicit BasePresenter(QObject *parent = nullptr);
virtual ~BasePresenter();
/**
* @brief 公用的初始化
* @return
*/
int Init();
/**
* @brief 开始检测
* @param cameraIndex 相机索引(-1表示所有相机)
* @param isAuto 是否自动模式
* @return 0: 成功, 其他: 错误码
*/
virtual int StartDetection(int cameraIndex = -1, bool isAuto = true);
/**
* @brief 停止检测
* @return 0: 成功, 其他: 错误码
*/
virtual int StopDetection();
/**
* @brief 获取相机列表
* @return 相机列表(IP地址和设备指针的pair)
*/
std::vector<std::pair<std::string, IVrEyeDevice*>> GetCameraList() const {
return m_vrEyeDeviceList;
}
/**
* @brief 设置默认相机索引
* @param cameraIndex 相机索引
*/
void SetDefaultCameraIndex(int cameraIndex) {
m_currentCameraIndex = cameraIndex;
}
/**
* @brief 获取默认相机索引
* @return 相机索引
*/
int GetDetectIndex() const {
return m_currentCameraIndex;
}
/**
* @brief 获取检测数据缓存大小
* @return 缓存中的数据个数
*/
int GetDetectionDataCacheSize() const;
/**
* @brief 保存当前缓存的检测数据到文件(便捷方法)
*
* 直接保存内部缓存的数据,无需传入参数
*
* @param filePath 文件路径
* @return 0: 成功, 其他: 错误码
*/
virtual int SaveDetectionDataToFile(const std::string& filePath);
/**
* @brief 加载调试数据并执行检测(virtual,子类可重写添加状态更新)
*
* 从文件加载激光扫描数据到缓存,然后执行算法检测
*
* @param filePath 文件路径
* @return 0: 成功, 其他: 错误码
*/
virtual int LoadDebugDataAndDetect(const std::string& filePath);
/**
* @brief 为所有相机设置状态回调
*
* 遍历所有相机设备,为每个设备设置状态回调函数
*
* @param fNotify 状态回调函数指针
* @param param 回调函数的用户参数
*/
void SetCameraStatusCallback(VzNL_OnNotifyStatusCBEx fNotify, void* param);
2026-07-30 12:06:02 +08:00
/**
* @brief 恢复每台相机由 Presenter 配置的状态回调及对应上下文
*/
void RestoreCameraStatusCallbacks();
2026-03-28 10:49:55 +08:00
/**
* @brief 获取当前工作状态
* @return 当前工作状态
*/
WorkStatus GetCurrentWorkStatus() const {
return m_currentWorkStatus;
}
/**
* @brief 设置工作状态(会触发状态回调通知)
* @param status 新的工作状态
*/
void SetWorkStatus(WorkStatus status);
2026-05-17 00:38:31 +08:00
/**
* @brief 设置扫描配置
* @param config 扫描配置
*/
void SetScanConfig(const VrScanConfig& config) { m_scanConfig = config; }
/**
* @brief 获取扫描配置
* @return 扫描配置
*/
VrScanConfig GetScanConfig() const { return m_scanConfig; }
2026-03-28 10:49:55 +08:00
// ============ ModbusTCP 服务相关 ============
/**
* @brief 检查ModbusTCP服务是否运行
* @return true: 运行中, false: 未运行
*/
bool IsModbusServerRunning() const;
/**
* @brief 写入Modbus保持寄存器(发送数据)
* @param startAddress 起始地址
* @param data 数据指针
* @param count 寄存器数量
* @return 0: 成功, 其他: 错误码
*/
int WriteModbusRegisters(uint16_t startAddress, const uint16_t* data, uint16_t count);
/**
* @brief 设置状态回调接口(模板方法)
*
* 存储状态回调接口指针,用于通知UI状态变化
* 子类可以直接调用此方法设置自己的状态回调接口
*
* @tparam StatusCallbackType 状态回调接口类型
* @param statusCallback 状态回调接口指针
*/
template<typename StatusCallbackType>
void SetStatusCallback(StatusCallbackType* statusCallback) {
m_pStatusCallback = static_cast<void*>(statusCallback);
}
/**
* @brief 获取状态回调接口(模板方法)
*
* 获取之前设置的状态回调接口指针
*
* @tparam StatusCallbackType 状态回调接口类型
* @return 状态回调接口指针
*/
template<typename StatusCallbackType>
StatusCallbackType* GetStatusCallback() const {
return static_cast<StatusCallbackType*>(m_pStatusCallback);
}
/**
* @brief 通用的静态检测数据回调函数
*
* 此静态函数可被所有子类使用,将检测数据添加到缓存
* 子类的 GetDetectionCallback() 可以直接返回此函数
*
* @param eDataType 数据类型
* @param pLaserLinePoint 激光线数据指针
* @param pUserData 用户数据(BasePresenter实例指针)
*/
static void _StaticDetectionCallback(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint, void* pUserData);
/**
* @brief 通用的静态相机状态回调函数
*
* 此静态函数可被所有子类使用,处理相机状态变化
* 子类的 GetCameraStatusCallback() 可以直接返回此函数
*
* @param eStatus 相机工作状态
* @param pExtData 扩展数据
* @param nDataLength 数据长度
* @param pInfoParam 参数(BasePresenter实例指针)
*/
static void _StaticCameraStatusCallback(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pInfoParam);
protected:
// ============ 纯虚函数 - 子类必须实现 ============
/**
* @brief 私有初始化子类应该重写此方法
* @return 0: 成功, 其他: 错误码
*/
virtual int InitApp() = 0;
/**
* @brief 初始化算法参数(纯虚函数)
*
* 子类需要在此方法中:
* - 加载算法参数配置
* - 初始化算法库
* - 加载标定参数
*
* @return 0: 成功, 其他: 错误码
*/
virtual int InitAlgoParams() = 0;
/**
* @brief 执行算法检测(纯虚函数)
*
* 子类需要在此方法中:
* - 使用传入的点云数据进行算法检测
* - 调用具体的算法库进行检测
* - 返回检测结果
*
* @param detectionDataCache 检测数据缓存的引用
* @return 0: 成功, 其他: 错误码
*/
virtual int ProcessAlgoDetection(std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& detectionDataCache) = 0;
// 应用需要保存预处理结果时,自行在 ProcessAlgoDetection 中提交保存。
virtual bool SavePointCloudBeforeProcessing() const
{
return true;
}
2026-03-28 10:49:55 +08:00
/**
* @brief 获取相机状态回调函数(虚函数,提供默认实现)
*
* 默认返回 BasePresenter 的通用静态回调函数
* 子类如果需要自定义相机状态处理,可以重写此方法
*
* @return 相机状态回调函数指针
*/
virtual VzNL_OnNotifyStatusCBEx GetCameraStatusCallback() {
return &BasePresenter::_StaticCameraStatusCallback;
}
/**
* @brief 获取检测数据回调函数(虚函数,提供默认实现)
*
* 默认返回 BasePresenter 的通用静态回调函数
* 子类如果需要自定义检测数据处理,可以重写此方法
*
* @return 检测数据回调函数指针
*/
virtual VzNL_AutoOutputLaserLineExCB GetDetectionCallback() {
return &BasePresenter::_StaticDetectionCallback;
}
/**
* @brief 处理一条相机回调数据。
* @return true 表示应用已经接管该数据,基类不再复制到批量缓存;
* false 使用基类默认的复制/缓存流程。
*/
virtual bool OnDetectionData(EVzResultDataType dataType,
SVzLaserLineData* laserData) {
Q_UNUSED(dataType);
Q_UNUSED(laserData);
return false;
}
/** @brief 应用侧是否有尚未提交算法的数据。 */
virtual bool HasPendingDetectionData() const {
return GetDetectionDataCacheSize() > 0;
}
2026-03-28 10:49:55 +08:00
/**
* @brief 获取检测数据类型(纯虚函数)
*
* 子类需要返回项目使用的数据类型,例如:
* - keResultDataType_Position (只要XYZ位置)
* - keResultDataType_PointXYZRGBA (需要RGBA颜色)
*
* @return 数据类型
*/
virtual EVzResultDataType GetDetectionDataType() = 0;
/**
* @brief 创建设备对象(虚函数,子类可重写)
*
* 默认创建VzNLSDK设备。子类如果需要使用其他设备(如GlLineLaserDevice),
* 可以重写此方法来创建对应的设备对象。
*
* @param ppDevice 输出设备指针
* @return 0: 成功, 其他: 错误码
*/
virtual int CreateDevice(IVrEyeDevice** ppDevice);
/**
* @brief 尝试重连相机(虚函数,提供默认实现)
*
* 基类提供通用的重连逻辑:
* - 遍历所有配置的相机
* - 检查是否已连接
* - 尝试调用 OpenDevice 重连
* - 调用 OnCameraStatusChanged 通知状态变化
* - 更新相机连接状态和默认相机索引
*
* 子类通常不需要重写此方法,除非有特殊的重连逻辑
*
* @return true: 所有相机已连接, false: 仍有相机未连接
*/
virtual bool TryReconnectCameras();
/**
* @brief 相机状态变化通知(纯虚函数)
*
* @param cameraIndex 相机索引
* @param isConnected 是否连接
*/
virtual void OnCameraStatusChanged(int cameraIndex, bool isConnected) = 0;
/**
* @brief 工作状态变化通知(虚函数,子类可选择重写)
*
* 当工作状态改变时,BasePresenter 会调用此方法通知子类
* 子类应在此方法中调用状态回调接口的 OnWorkStatusChanged
*
* @param status 新的工作状态
*/
virtual void OnWorkStatusChanged(WorkStatus status) {
// 默认空实现,子类可以重写以调用UI回调
}
/**
* @brief 相机数量变化通知(虚函数,子类可选择重写)
*
* 当相机初始化时,BasePresenter 会调用此方法通知子类相机数量
* 子类应在此方法中调用状态回调接口的 OnCameraCountChanged
*
* @param count 相机数量
*/
virtual void OnCameraCountChanged(int count) {
// 默认空实现,子类可以重写以调用UI回调
}
/**
* @brief 状态文字更新通知(虚函数,子类可选择重写)
*
* 当需要更新状态文字时,BasePresenter 会调用此方法
* 子类应在此方法中调用状态回调接口的 OnStatusUpdate
*
* @param statusMessage 状态消息
*/
virtual void OnStatusUpdate(const std::string& statusMessage) {
// 默认空实现,子类可以重写以调用UI回调
}
/**
* @brief ModbusTCP服务器状态变化通知(虚函数,子类可选择重写)
*
* 当ModbusTCP客户端连接或断开时,BasePresenter 会调用此方法
* 子类应在此方法中调用状态回调接口的 OnRobotConnectionChanged
*
* @param isConnected true=客户端已连接,false=客户端已断开
*/
virtual void OnModbusServerStatusChanged(bool isConnected) {
// 默认空实现,子类可以重写以调用UI回调
}
/**
* @brief Modbus写寄存器回调(虚函数,子类可选择重写)
*
* 当收到Modbus写寄存器命令时调用,子类可以重写以实现特定控制逻辑
*
* @param startAddress 起始寄存器地址
* @param data 写入的数据
* @param count 寄存器数量
*/
virtual void OnModbusWriteCallback(uint16_t startAddress, const uint16_t* data, uint16_t count) {
// 默认空实现,子类可以重写以处理写寄存器命令
}
/**
* @brief 获取调试参数(虚函数,子类可选择重写)
*
* 子类可以重写此方法,返回从配置文件加载的调试参数
* 基类提供默认的调试参数
*
* @return 调试参数
*/
virtual VrDebugParam GetDebugParam() const {
return m_debugParam;
}
/**
* @brief 是否应该启动ModbusTCP Server(虚函数,子类可选择重写)
*
* 默认返回true,子类可以重写为false以跳过Server启动,
* 例如需要使用ModbusTCP Client模式的应用。
*
* @return true: 启动Server, false: 跳过Server启动
*/
virtual bool ShouldStartModbusServer() const { return true; }
/**
* @brief ModbusTCP Server 监听端口
*
* 默认保持历史端口 5020。需要从项目配置读取端口的应用可重写该函数。
*/
virtual int GetModbusServerPort() const { return 5020; }
2026-03-28 10:49:55 +08:00
/**
* @brief 设置调试参数
*
* 子类在初始化时调用此方法设置调试参数
*
* @param debugParam 调试参数
*/
2026-07-03 14:11:39 +08:00
void SetDebugParam(const VrDebugParam& debugParam);
void SetDebugDataSaveQueueSize(int maxQueueSize);
2026-03-28 10:49:55 +08:00
/**
* @brief 保存点云文件时附加在设备名后的标签(虚函数,子类可选择重写)
*
* 子类可根据当前工作模式返回标签字符串(如 "轮毂"/"工装"),
* 标签会以 "_{tag}" 形式拼接到 deviceName 后,仅用于保存文件名,
* 不影响 m_vrEyeDeviceList 中的原始相机名。返回空串表示不附加。
*
* @return 模式标签字符串
*/
virtual std::string GetSaveDeviceTag() const { return ""; }
2026-03-28 10:49:55 +08:00
// ============ 公共实现方法 ============
/**
* @brief 初始化相机设备(公共实现)
*
* 此方法实现了通用的相机初始化逻辑:
* - 保存相机配置
* - 初始化相机列表
* - 调用 OpenDevice 打开每个相机
* - 启动重连定时器(如果需要)
*
* @param cameraList 相机配置列表
* @param bRGB 是否启用RGB
* @param bSwing 是否启用摆动
* @return 0: 成功, 其他: 错误码
*/
virtual int InitCamera(std::vector<DeviceInfo>& cameraList, bool bRGB, bool bSwing);
/**
* @brief 清空检测数据缓存(virtual允许子类重写以添加特殊清理逻辑)
*/
virtual void ClearDetectionDataCache();
/**
* @brief 添加检测数据到缓存
* @param dataType 数据类型
* @param laserData 激光数据
*/
void AddDetectionDataToCache(EVzResultDataType dataType, const SVzLaserLineData& laserData);
/**
* @brief 获取检测数据缓存的引用(用于批量加载数据)
*
* 提供线程安全的方式访问缓存进行批量操作
* 调用者需要在调用此方法的作用域内完成所有操作
*
* @param func 操作函数,接收缓存引用作为参数,返回操作结果
* @return func 的返回值
*/
template<typename Func>
int AccessDetectionDataCache(Func func) {
std::lock_guard<std::mutex> lock(m_detectionDataMutex);
return func(m_detectionDataCache);
}
private:
/**
* @brief 打开相机设备(公共实现)
*
* 此方法实现了通用的相机打开逻辑:
* - 创建 IVrEyeDevice 对象
* - 初始化设备
* - 设置状态回调(调用 GetCameraStatusCallback)
* - 打开相机(使用 GetOpenDeviceMode)
* - 存储到设备列表
*
* @param cameraIndex 相机索引(从1开始)
* @param cameraName 相机名称
* @param cameraIp 相机IP地址
* @return 0: 成功, 其他: 错误码
*/
int OpenDevice(int cameraIndex, const char* cameraName, const char* cameraIp, bool bRGB, bool bSwing);
/**
* @brief 算法检测线程函数
*
* 实现了通用的检测线程循环:
* - 等待检测触发
* - 调用 ProcessAlgoDetection 执行算法
* - 处理检测结果
*/
void AlgoDetectThreadFunc();
/**
* @brief 检测任务主流程
*
* 实现了通用的检测任务流程:
* - 验证数据缓存
* - 调用 ProcessAlgoDetection 执行算法
* - 更新工作状态
*
* @return 0: 成功, 其他: 错误码
*/
int DetectTask();
// 同步覆盖保存现场点云,保存成功后才能进入算法。
int SaveCrashDataBeforeProcessing(const VrDebugParam& debugParam, const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& detectionData);
2026-03-28 10:49:55 +08:00
/**
* @brief 启动算法检测线程
*/
void StartAlgoDetectThread();
/**
* @brief 停止算法检测线程
*/
void StopAlgoDetectThread();
/**
* @brief 启动相机重连定时器
*/
void StartCameraReconnectTimer();
/**
* @brief 停止相机重连定时器
*/
void StopCameraReconnectTimer();
2026-05-17 00:38:31 +08:00
// ============ 多相机同时扫描相关方法 ============
/**
* @brief 添加数据到指定相机的独立缓存(同时扫描模式专用)
* @param cameraIndex 相机索引(1-based)
* @param dataType 数据类型
* @param laserData 激光数据
*/
void AddDetectionDataToCameraCache(int cameraIndex, EVzResultDataType dataType,
const SVzLaserLineData& laserData);
/**
* @brief 清空所有相机的独立数据缓存
*/
void ClearAllCameraDataCaches();
/**
* @brief 相机扫描完成通知(Swing_Finish 时调用)
*
* 增加批次完成计数
*
* @param cameraIndex 完成扫描的相机索引
*/
void OnCameraScanFinished(int cameraIndex);
/**
* @brief 启动一批相机的扫描
*
* 从 m_batchStartIndex 开始,取 m_batchSize 个相机同时启动扫描
*/
void StartBatchScan();
/**
* @brief 检查当前批次是否全部完成并逐个处理
*
* 在算法线程中调用,当前批次全部扫描完成后逐个处理每个相机的数据,
* 处理完后自动推进到下一批或结束
*/
void ProcessBatchIfReady();
2026-03-28 10:49:55 +08:00
private slots:
/**
* @brief 相机重连定时器触发
*/
void OnCameraReconnectTimer();
protected:
struct DebugDataSaveContext
{
std::string basePath;
std::string timestamp;
std::string deviceName;
std::string filePrefix;
};
DebugDataSaveContext CreateDebugDataSaveContext(const VrDebugParam& debugParam, int cameraIndex) const;
virtual DebugDataSaveContext GetDebugDataSaveContext(const VrDebugParam& debugParam) const;
// Project four 3D vertices into a camera image and draw a closed contour
// in vertex order (0->1->2->3->0).
void DrawProjectedBin(QImage& image,
int cameraIndex,
const SVzNL3DPoint points3D[4],
int imageOriginX = 0,
int imageOriginY = 0) const;
2026-07-30 12:06:02 +08:00
// 派生析构可在释放自身回调资源前停止基类异步设施。
void StopCameraReconnectForDerivedTeardown() { StopCameraReconnectTimer(); }
void StopBaseModbusServer() { StopModbusServer(); }
2026-03-28 10:49:55 +08:00
// 相机设备列表
std::vector<std::pair<std::string, IVrEyeDevice*>> m_vrEyeDeviceList;
// 当前相机索引
int m_currentCameraIndex = 0;
// 连接状态标志
bool m_bCameraConnected = false;
// 算法检测线程相关
std::atomic<bool> m_bAlgoDetectThreadRunning;
2026-07-30 12:06:02 +08:00
std::atomic<bool> m_algoDetectTaskPending{false};
2026-03-28 10:49:55 +08:00
std::mutex m_algoDetectMutex;
std::condition_variable m_algoDetectCondition;
std::thread m_algoDetectThread;
// 检测数据缓存
std::mutex m_detectionDataMutex;
std::vector<std::pair<EVzResultDataType, SVzLaserLineData>> m_detectionDataCache;
// 激光数据加载器
LaserDataLoader m_dataLoader;
// 相机重连定时器
QTimer* m_pCameraReconnectTimer;
std::vector<DeviceInfo> m_expectedList;
bool m_bRGB{false};
bool m_bSwing{true};
// 工作状态管理
WorkStatus m_currentWorkStatus = WorkStatus::InitIng;
// 状态回调接口(使用void*存储,避免模板化整个类)
void* m_pStatusCallback = nullptr;
// ModbusTCP服务器
IYModbusTCPServer* m_modbusServer = nullptr;
// 初始化线程
std::thread m_initThread;
// 调试参数
VrDebugParam m_debugParam;
// 调试数据异步存储
DebugDataSaver m_debugDataSaver;
2026-05-17 00:38:31 +08:00
// ============ 多相机同时扫描相关成员 ============
// 扫描配置
VrScanConfig m_scanConfig;
// 每个相机的回调上下文(key = 相机索引 1-based)
std::map<int, CameraCallbackContext*> m_cameraContexts;
// 每个相机的独立数据缓存区(key = 相机索引 1-based)
std::map<int, std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>> m_perCameraDataCache;
std::mutex m_perCameraDataMutex;
// 分批扫描调度相关
std::vector<int> m_batchCameraList; // 本轮需扫描的相机列表
int m_batchStartIndex = 0; // 当前批次起始位置
int m_batchSize = 0; // 当前批次相机数
int m_batchFinishedCount = 0; // 当前批次已完成计数
2026-05-17 19:00:47 +08:00
std::atomic<bool> m_batchInProgress{false}; // 是否正在分批扫描(决定数据/状态回调的分流路径)
2026-05-17 00:38:31 +08:00
std::mutex m_batchStateMutex; // 保护分批调度状态
2026-03-28 10:49:55 +08:00
private:
// 启动ModbusTCP服务(在Init中调用)
int StartModbusServer(int port = 5020);
// 停止ModbusTCP服务
void StopModbusServer();
// Modbus写寄存器回调处理(返回0表示成功)
int OnModbusWriteRegisters(uint8_t unitId, uint16_t startAddress,
uint16_t quantity, const uint16_t* values);
};
#endif // BASEPRESENTER_H