GrabBag/App/DroneScrewbolt/DroneScrewServer/DroneScrewServerPresenter.h
2026-06-26 17:55:15 +08:00

308 lines
11 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.

#ifndef DRONESCREW_SERVER_PRESENTER_H
#define DRONESCREW_SERVER_PRESENTER_H
#include <QObject>
#include <QJsonObject>
#include <QString>
#include <QTimer>
#include <QMutex>
#include <QDateTime>
#include <atomic>
#include <thread>
#include <memory>
#include <mutex>
#include <vector>
#include "IMvsDevice.h"
#include "IVrFFMediaPusher.h"
#include "Algo/IDroneScrewAlgo.h"
/**
* @brief 双板架构「板一」 Presenter双目版本
*
* 流程:
* 双目海康MVS相机采集 → 算法检测(左右目输入)→ 画检测框 → 推送至 RTSP 服务 → ZMQ 异步发布结果
*
* 架构特点:
* - 使用 IMvsDevice 双目相机(左右目)
* - 左右目图像同步等待机制(参考 BinocularMarkPresenter
* - 算法检测需要左右目双目输入
* - RTMP/RTSP 推流推送 Rtsp.streamCamera 指定的单路图像
* - ZMQ 原始图像订阅发送左右目两张图像(或仅左目单张图像,由 detectMode 控制)
*
* detectMode 说明:
* - "binocular"默认ZMQ 原始图像 PUB 发送左右目两张图像BIWI 协议头)
* - "mono"ZMQ 原始图像 PUB 仅发送左目图像BIWI 协议头,右目置空)
* 注意mono 模式下算法仍使用双目输入,仅传输层面减少右目数据量
*/
class DroneScrewServerPresenter : public QObject
{
Q_OBJECT
public:
explicit DroneScrewServerPresenter(QObject* parent = nullptr);
~DroneScrewServerPresenter() override;
/**
* @brief 加载配置文件
*/
bool loadConfiguration(const QString& configFilePath);
/**
* @brief 设置 saveConfiguration 的目标路径
*
* 运行时调用此方法可以修改保存位置,主要用于将用户配置持久化到
* 独立文件中(如 runtime_config.xml避免被部署包覆盖。
*/
void setConfigFilePath(const QString& path) { m_configFilePath = path; }
/**
* @brief 保存当前配置到 m_configFilePath
*
* 保存 Camera曝光/增益)和 Trigger 等运行时参数。
*/
bool saveConfiguration();
/**
* @brief 初始化相机 / 推流 / 算法
*/
int initAll();
/**
* @brief 反初始化
*/
void deinitAll();
/**
* @brief 当前检测中
*/
bool isDetecting() const { return m_bIsDetecting.load(); }
bool isLiveStreaming() const { return m_bLiveStreaming.load(); }
/**
* @brief 同步执行单次检测,并返回真实执行结果
*
* 供 ZMQ REP 的 single 命令使用。失败时返回 success=false
* errorCode/message 描述失败原因。
*/
DroneScrewResult runSingleDetection();
/**
* @brief 获取 ZMQ REP / PUB 端口(供主程序启动 ZMQ 协议层)
*/
int getZmqControlPort() const { return m_zmqControlPort; }
int getZmqResultPort() const { return m_zmqResultPort; }
int getZmqRawImagePort() const { return m_zmqRawImagePort; }
QString getRtspUrl() const { return m_rtspAdvertiseUrl; }
QJsonObject getRuntimeInfo() const;
QJsonObject getCalibrationInfo() const;
int startDetectionWork();
int stopDetectionWork();
int startLiveStream();
int stopLiveStream();
int swapCameraRoles();
/**
* @brief 启用 / 停用 ZMQ 原始图像 PUB
*
* ZMQ 协议层启动 raw PUB 端口后会调用 Enable
* 之后采集回调里会 emit rawImageReady。
*/
void setRawPubEnabled(bool enabled) { m_bRawPubEnabled = enabled; }
/**
* @brief 设置检测模式下的图像传输模式
* @param mode "binocular"(默认,发送左右目)或 "mono"(仅发送左目)
*
* 仅影响 ZMQ 原始图像 PUB 通道的传输内容;
* 算法内部始终使用双目输入。
*/
void setDetectMode(const std::string& mode);
std::string detectMode() const;
public slots:
/**
* @brief 设置曝光(同时设置左右相机)
*/
void handleSetExposure(double exposureTime);
/**
* @brief 设置增益(同时设置左右相机)
*/
void handleSetGain(double gain);
/**
* @brief 设置左相机曝光
*/
void handleSetLeftExposure(double exposureTime);
/**
* @brief 设置右相机曝光
*/
void handleSetRightExposure(double exposureTime);
/**
* @brief 设置左相机增益
*/
void handleSetLeftGain(double gain);
/**
* @brief 设置右相机增益
*/
void handleSetRightGain(double gain);
/**
* @brief 更新算法参数
*/
void handleUpdateAlgoParams(const DroneScrewAlgoParams& params);
signals:
/**
* @brief 单帧检测结果信号(提供给 ZMQ 协议层异步发布)
*/
void detectionResult(const DroneScrewResult& result);
/**
* @brief 原始双目相机帧信号
*
* 仅当 m_bRawPubEnabled 为 true 时才会 emit供 ZMQ 协议层广播给客户端。
* 注意MvsImageData 内部的 pData 由 presenter 管理,槽函数收到后请立即处理
* 或拷贝;本对象只保证在同步 emit 期间内有效。
*/
void rawImageReady(const MvsImageData& leftImg, const MvsImageData& rightImg);
/**
* @brief 状态/事件信号
*/
void statusChanged(const QString& msg);
private slots:
/**
* @brief 相机重连定时器
*/
void onCameraReconnectTimer();
private:
int tryConnectCamera();
void closeCamera();
int configureCamera(IMvsDevice* camera,
const char* role,
bool ioTrigger = false,
double frameRate = 2.0,
int64_t binningHorizontal = 1,
int64_t binningVertical = 1);
IMvsDevice* cameraForRole(const char* role) const;
const char* liveStreamCameraRole() const;
IMvsDevice* liveStreamCamera() const;
IMvsDevice* nonLiveStreamCamera() const;
bool isLiveStreamCameraRole(const char* role) const;
bool areCamerasGrabbing();
int prepareDetectionAcquisition(double frameRate,
int64_t binningHorizontal,
int64_t binningVertical,
const char* tag);
int prepareLiveStreamAcquisition();
void resetFrameReadyFlags();
int initRtspPusher(unsigned int width, unsigned int height, unsigned int fps);
void releaseRtspPusher();
int pushRtspFrame(const MvsImageData& img);
bool tryRgaScaleMonoToY(const MvsImageData& img,
unsigned char* dstY,
unsigned int outWidth,
unsigned int outHeight);
void leftCameraCallback(const MvsImageData& img);
void rightCameraCallback(const MvsImageData& img);
void detectThreadFunc();
double exposureForRole(const char* role) const;
double gainForRole(const char* role) const;
int pulseGpioTrigger(bool logOk = true);
void startGpioTriggerLoop();
void stopGpioTriggerLoop();
void triggerThreadFunc();
bool waitAndCopyLatestBinocular(MvsImageData& leftImg,
MvsImageData& rightImg,
unsigned int timeoutMs);
private:
// 双目相机设备
IMvsDevice* m_pLeftCamera{nullptr};
IMvsDevice* m_pRightCamera{nullptr};
// 推流
IVrFFMediaPusher* m_pPusher{nullptr};
// 算法
std::unique_ptr<IDroneScrewAlgo> m_pAlgo;
DroneScrewAlgoParams m_algoParams;
// 状态
std::atomic<bool> m_bCameraConnected{false};
std::atomic<bool> m_bIsDetecting{false};
QTimer* m_pReconnectTimer{nullptr};
// 配置
std::string m_strLeftCameraSerial;
std::string m_strRightCameraSerial;
unsigned int m_nLeftCameraIndex{0};
unsigned int m_nRightCameraIndex{1};
double m_leftExposureTime{10000.0};
double m_rightExposureTime{10000.0};
double m_leftGain{1.0};
double m_rightGain{1.0};
QString m_configFilePath;
int m_zmqControlPort{5555};
int m_zmqResultPort{5556};
int m_zmqRawImagePort{5557};
int m_rtspPort{8554};
QString m_rtspPath{"/live/dronescrew"};
QString m_rtspAdvertiseUrl;
int m_pushBitrateKbps{4096};
int m_pushFps{30};
int m_liveStreamFps{30};
unsigned int m_liveStreamWidth{1024};
unsigned int m_liveStreamHeight{750};
std::string m_liveStreamCameraRole{"left"}; // fixed to left for realtime stream
bool m_useIoTrigger{true};
int m_triggerGpio{113};
int m_triggerSource{0}; // MVS Line0
int m_triggerActivation{0}; // MVS RisingEdge
std::thread m_triggerThread;
std::atomic<bool> m_bTriggerThreadExit{false};
unsigned int m_rtspWidth{0};
unsigned int m_rtspHeight{0};
std::atomic<bool> m_bRtspStarted{false};
std::atomic<bool> m_bLiveStreaming{false};
std::atomic<int> m_detectPipelineMode{0}; // 0=precision, 1=distance
std::atomic<int> m_activeTriggerFps{2};
std::atomic<int64_t> m_rtspFrameCounter{0}; // 相对帧计数(推流启动时重置)
std::vector<unsigned char> m_rtspFrameBuffer;
std::vector<unsigned int> m_rtspScaleX;
std::vector<unsigned int> m_rtspScaleY;
unsigned int m_rtspScaleSrcWidth{0};
unsigned int m_rtspScaleSrcHeight{0};
unsigned int m_rtspScaleOutWidth{0};
unsigned int m_rtspScaleOutHeight{0};
bool m_rtspUvInitialized{false};
bool m_rtspRgaScaleDisabled{false};
bool m_rtspRgaScaleLogged{false};
// 原始图像 PUB 开关
std::atomic<bool> m_bRawPubEnabled{false};
// 检测模式下的图像传输模式
mutable std::mutex m_detectModeMutex;
std::string m_detectMode{"binocular"}; // "binocular" | "mono"
// 检测线程
std::thread m_detectThread;
std::atomic<bool> m_bThreadExit{false};
QMutex m_frameMutex;
MvsImageData m_leftImageData;
MvsImageData m_rightImageData;
bool m_bLeftImageReady{false};
bool m_bRightImageReady{false};
};
#endif // DRONESCREW_SERVER_PRESENTER_H