From ba586bf2eeb4782eb76337150609f2c2c999549a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=B0=E4=BB=94?= Date: Tue, 15 Jul 2025 00:45:05 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=B2=E5=8F=A3=E6=8E=A5?= =?UTF-8?q?=E6=94=B6=E7=BA=BF=E7=A8=8B=E6=94=AF=E6=8C=81=EF=BC=8C=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E4=B8=B2=E5=8F=A3=E6=95=B0=E6=8D=AE=E5=A4=84=E7=90=86?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- GrabBagApp/GrabBagApp.pro | 9 + GrabBagApp/Presenter/Inc/SerialProtocol.h | 31 +++ GrabBagApp/Presenter/Src/GrabBagPresenter.cpp | 40 ++-- GrabBagApp/Presenter/Src/SerialProtocol.cpp | 224 +++++++++++++----- GrabBagApp/Version.h | 4 +- GrabBagApp/Version.md | 8 - GrabBagPrj/Version/Version.md | 42 ++-- Module/ShareMem/Inc/IVrShareMem.h | 8 +- Module/ShareMem/ShareMem.pro | 3 +- Module/ShareMem/Src/VrShareMem.cpp | 6 +- VrConfig/Inc/IVrConfig.h | 4 + 11 files changed, 268 insertions(+), 111 deletions(-) delete mode 100644 GrabBagApp/Version.md diff --git a/GrabBagApp/GrabBagApp.pro b/GrabBagApp/GrabBagApp.pro index a19e742..44bc750 100644 --- a/GrabBagApp/GrabBagApp.pro +++ b/GrabBagApp/GrabBagApp.pro @@ -142,6 +142,15 @@ win32 { LIBS += Advapi32.lib } +# Default rules for deployment. +unix { + target.path = /usr/lib + # Link real-time library for POSIX shared memory functions + LIBS += -lrt +} +!isEmpty(target.path): INSTALLS += target + + # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin diff --git a/GrabBagApp/Presenter/Inc/SerialProtocol.h b/GrabBagApp/Presenter/Inc/SerialProtocol.h index 28b02a9..96b3315 100644 --- a/GrabBagApp/Presenter/Inc/SerialProtocol.h +++ b/GrabBagApp/Presenter/Inc/SerialProtocol.h @@ -6,6 +6,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include "ProtocolCommon.h" #include @@ -114,11 +120,36 @@ private: */ std::string BuildDataProtocol(const MultiTargetData& multiTargetData, uint16_t cameraId); + /** + * @brief 阻塞式串口数据接收线程函数 + */ + void SerialReceiveThreadFunction(); + + /** + * @brief 启动串口接收线程 + */ + void StartReceiveThread(); + + /** + * @brief 停止串口接收线程 + */ + void StopReceiveThread(); + + /** + * @brief 处理接收到的数据(线程安全) + */ + void ProcessReceivedData(); + private: QSerialPort* m_pSerialPort; // 串口对象 SerialStatus m_serialStatus; // 串口状态 QString m_receiveBuffer; // 接收缓冲区 + // 线程相关 + std::thread m_receiveThread; // 接收线程 + std::atomic m_threadRunning; // 线程运行标志 + std::mutex m_bufferMutex; // 缓冲区互斥锁 + // 回调函数 ConnectionCallback m_connectionCallback; // 连接状态回调 WorkSignalCallback m_workSignalCallback; // 工作信号回调 diff --git a/GrabBagApp/Presenter/Src/GrabBagPresenter.cpp b/GrabBagApp/Presenter/Src/GrabBagPresenter.cpp index 4c22f91..d23cc21 100644 --- a/GrabBagApp/Presenter/Src/GrabBagPresenter.cpp +++ b/GrabBagApp/Presenter/Src/GrabBagPresenter.cpp @@ -228,7 +228,7 @@ int GrabBagPresenter::Init() m_pStatus->OnStatusUpdate("设备初始化完成"); CheckAndUpdateWorkStatus(); - +#if 0 // 初始化配置管理器 m_pConfigManager = std::make_unique(); if (!m_pConfigManager->Initialize(configPath.toStdString())) { @@ -239,7 +239,7 @@ int GrabBagPresenter::Init() // 注册为配置变化监听器 m_pConfigManager->AddConfigChangeListener(std::shared_ptr(this, [](IConfigChangeListener*){})); - +#endif m_pStatus->OnStatusUpdate("配置管理器初始化成功"); LOG_INFO("ConfigManager initialized successfully\n"); @@ -947,26 +947,26 @@ void GrabBagPresenter::_SendDetectionResultToRobot(const DetectionResult& detect if (m_pStatus) { m_pStatus->OnStatusUpdate("没有检测到目标,发送空的多目标数据"); } - return; - } - - // 获取检测到的目标位置(已经是机械臂坐标系) - const auto& positions = detectionResult.positions; - multiTargetData.count = static_cast(positions.size()); + // 即使检测结果为0,也要发送空数据,所以不return,继续执行后面的发送逻辑 + } else { + // 获取检测到的目标位置(已经是机械臂坐标系) + const auto& positions = detectionResult.positions; + multiTargetData.count = static_cast(positions.size()); - // 直接使用已经转换好的机械臂坐标 - for (size_t i = 0; i < positions.size(); i++) { - const GrabBagPosition& pos = positions[i]; - - // 直接使用已转换的坐标数据 - TargetPosition robotTarget; - robotTarget.x = pos.x; // 已转换的X轴坐标 - robotTarget.y = pos.y; // 已转换的Y轴坐标 - robotTarget.z = pos.z; // 已转换的Z轴坐标 - robotTarget.rz = pos.yaw; // Yaw角 + // 直接使用已经转换好的机械臂坐标 + for (size_t i = 0; i < positions.size(); i++) { + const GrabBagPosition& pos = positions[i]; + + // 直接使用已转换的坐标数据 + TargetPosition robotTarget; + robotTarget.x = pos.x; // 已转换的X轴坐标 + robotTarget.y = pos.y; // 已转换的Y轴坐标 + robotTarget.z = pos.z; // 已转换的Z轴坐标 + robotTarget.rz = pos.yaw; // Yaw角 - // 添加到多目标数据 - multiTargetData.targets.push_back(robotTarget); + // 添加到多目标数据 + multiTargetData.targets.push_back(robotTarget); + } } // 发送到机械臂(网络ModbusTCP) diff --git a/GrabBagApp/Presenter/Src/SerialProtocol.cpp b/GrabBagApp/Presenter/Src/SerialProtocol.cpp index 9179f12..75a9e49 100644 --- a/GrabBagApp/Presenter/Src/SerialProtocol.cpp +++ b/GrabBagApp/Presenter/Src/SerialProtocol.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include @@ -19,16 +21,20 @@ const QString SerialProtocol::SEPARATOR = ","; SerialProtocol::SerialProtocol(QObject* parent) : QObject(parent) , m_serialStatus(STATUS_DISCONNECTED) + , m_threadRunning(false) { m_pSerialPort = new QSerialPort(this); - // 连接信号和槽 + // 连接信号和槽(保留原有的信号槽机制作为备用) connect(m_pSerialPort, &QSerialPort::readyRead, this, &SerialProtocol::OnSerialDataReceived); connect(m_pSerialPort, &QSerialPort::errorOccurred, this, &SerialProtocol::OnSerialError); } SerialProtocol::~SerialProtocol() { + // 先停止接收线程 + StopReceiveThread(); + // 再关闭串口 CloseSerial(); } @@ -96,6 +102,9 @@ int SerialProtocol::OpenSerial(const std::string& portName, int baudRate, m_serialStatus = STATUS_CONNECTED; LOG_INFO("Serial port opened successfully: %s\n", portName.c_str()); + // 启动接收线程 + StartReceiveThread(); + // 触发连接状态回调 if (m_connectionCallback) { m_connectionCallback(true); @@ -108,6 +117,11 @@ void SerialProtocol::CloseSerial() { if (m_pSerialPort && m_pSerialPort->isOpen()) { LOG_INFO("Closing serial port: %s\n", m_pSerialPort->portName().toStdString().c_str()); + + // 先停止接收线程 + StopReceiveThread(); + + // 再关闭串口 m_pSerialPort->close(); m_serialStatus = STATUS_DISCONNECTED; @@ -163,29 +177,32 @@ bool SerialProtocol::IsOpen() const void SerialProtocol::OnSerialDataReceived() { - LOG_DEBUG("OnSerialDataReceived() called\n"); - LOG_DEBUG("OnSerialDataReceived() called\n"); - LOG_DEBUG("OnSerialDataReceived() called\n"); - LOG_DEBUG("OnSerialDataReceived() called\n"); - LOG_DEBUG("OnSerialDataReceived() called\n"); + // 注意:这个方法现在主要作为Qt信号槽的备用机制 + // 主要的数据接收由独立线程处理 + LOG_DEBUG("[Signal] OnSerialDataReceived() called (backup mechanism)\n"); + // 如果线程正在运行,优先使用线程处理 + if (m_threadRunning.load()) { + LOG_DEBUG("[Signal] Thread is running, skipping signal-based processing\n"); + return; + } if (!m_pSerialPort) { - LOG_ERROR("Serial port is null in OnSerialDataReceived\n"); + LOG_ERROR("[Signal] Serial port is null in OnSerialDataReceived\n"); return; } if (!m_pSerialPort->isOpen()) { - LOG_ERROR("Serial port is not open in OnSerialDataReceived\n"); + LOG_ERROR("[Signal] Serial port is not open in OnSerialDataReceived\n"); return; } // 检查可用字节数 qint64 bytesAvailable = m_pSerialPort->bytesAvailable(); - LOG_INFO("Bytes available to read: %lld\n", bytesAvailable); + LOG_INFO("[Signal] Bytes available to read: %lld\n", bytesAvailable); if (bytesAvailable <= 0) { - LOG_WARNING("No bytes available but readyRead signal was triggered\n"); + LOG_WARNING("[Signal] No bytes available but readyRead signal was triggered\n"); return; } @@ -193,18 +210,21 @@ void SerialProtocol::OnSerialDataReceived() QByteArray data = m_pSerialPort->readAll(); QString receivedData = QString::fromUtf8(data); - LOG_INFO("Actually read %d bytes from serial port\n", data.size()); + LOG_INFO("[Signal] Actually read %d bytes from serial port\n", data.size()); if (data.isEmpty()) { - LOG_WARNING("readAll() returned empty data\n"); + LOG_WARNING("[Signal] readAll() returned empty data\n"); return; } - // 添加到接收缓冲区 - m_receiveBuffer += receivedData; + // 使用互斥锁保护缓冲区 + { + std::lock_guard lock(m_bufferMutex); + m_receiveBuffer += receivedData; + } // 打印接收到的数据 - LOG_INFO("Received [%d bytes]: \"%s\"\n", data.size(), receivedData.toStdString().c_str()); + LOG_INFO("[Signal] Received [%d bytes]: \"%s\"\n", data.size(), receivedData.toStdString().c_str()); // 如果包含不可打印字符,额外打印ASCII码 bool hasNonPrintable = false; @@ -224,34 +244,11 @@ void SerialProtocol::OnSerialDataReceived() asciiInfo += QString("[%1] ").arg((unsigned char)c); } } - LOG_INFO("%s\n", asciiInfo.toStdString().c_str()); + LOG_INFO("[Signal] %s\n", asciiInfo.toStdString().c_str()); } - // 查找完整的协议帧 - int startIndex = -1; - int endIndex = -1; - - while ((startIndex = m_receiveBuffer.indexOf(PROTOCOL_START)) != -1) { - endIndex = m_receiveBuffer.indexOf(PROTOCOL_END, startIndex); - if (endIndex != -1) { - // 提取完整的协议帧 - QString protocolFrame = m_receiveBuffer.mid(startIndex, endIndex - startIndex + 1); - - // 解析协议数据 - ParseProtocolData(protocolFrame); - - // 移除已处理的数据 - m_receiveBuffer.remove(0, endIndex + 1); - } else { - // 没有找到结束标识,等待更多数据 - break; - } - } - - // 如果缓冲区太大,清空一部分(防止内存溢出) - if (m_receiveBuffer.length() > 1024) { - m_receiveBuffer.clear(); - } + // 处理接收到的数据 + ProcessReceivedData(); } void SerialProtocol::OnSerialError(QSerialPort::SerialPortError error) @@ -331,22 +328,32 @@ void SerialProtocol::ParseProtocolData(const QString& data) // 按分隔符分割数据 QStringList parts = cleanData.split(SEPARATOR); - if (parts.isEmpty()) { - LOG_WARNING("Empty protocol data received\n"); + // 移除空的部分 + QStringList validParts; + for (const QString& part : parts) { + if (!part.isEmpty()) { + validParts.append(part); + } + } + + + if (validParts.isEmpty()) { + LOG_WARNING("No valid parts found in protocol data\n"); return; } - QString command = parts[0]; + QString command = validParts[0]; + LOG_DEBUG("Extracted command: '%s'\n", command.toStdString().c_str()); if (command == CMD_START) { - // 处理开始命令:$,START,# + // 处理开始命令:$,START,# 或 $,START,cameraId,# LOG_INFO("Received START command\n"); // 默认相机ID为1,如果有更多参数可以解析 int cameraId = 1; - if (parts.size() > 1) { + if (validParts.size() > 1) { bool ok; - cameraId = parts[1].toInt(&ok); + cameraId = validParts[1].toInt(&ok); if (!ok) { cameraId = 1; } @@ -355,8 +362,7 @@ void SerialProtocol::ParseProtocolData(const QString& data) // 触发工作信号回调 if (m_workSignalCallback) { bool result = m_workSignalCallback(true, cameraId); - LOG_INFO("Work signal callback result: %s, camera ID: %d\n", - result ? "success" : "failed", cameraId); + LOG_INFO("Work signal callback result: %s, camera ID: %d\n", result ? "success" : "failed", cameraId); } m_serialStatus = STATUS_WORKING; @@ -400,11 +406,6 @@ int SerialProtocol::SendData(const std::string& data) // 立即刷新缓冲区 m_pSerialPort->flush(); - // 等待数据发送完成 - if (!m_pSerialPort->waitForBytesWritten(3000)) { - LOG_ERROR("Timeout waiting for data to be written\n"); - return -1; - } LOG_DEBUG("Sent %lld bytes to serial port\n", bytesWritten); return SUCCESS; @@ -432,3 +433,118 @@ std::string SerialProtocol::BuildDataProtocol(const MultiTargetData& multiTarget return ss.str(); } + +// 线程方法实现 +void SerialProtocol::StartReceiveThread() +{ + if (m_threadRunning.load()) { + LOG_WARNING("Receive thread is already running\n"); + return; + } + + LOG_INFO("Starting serial receive thread\n"); + m_threadRunning.store(true); + + // 启动接收线程 + m_receiveThread = std::thread(&SerialProtocol::SerialReceiveThreadFunction, this); +} + +void SerialProtocol::StopReceiveThread() +{ + if (!m_threadRunning.load()) { + LOG_DEBUG("Receive thread is not running\n"); + return; + } + + LOG_INFO("Stopping serial receive thread\n"); + m_threadRunning.store(false); + + // 等待线程结束 + if (m_receiveThread.joinable()) { + m_receiveThread.join(); + LOG_INFO("Serial receive thread stopped\n"); + } +} + +void SerialProtocol::SerialReceiveThreadFunction() +{ + LOG_INFO("Serial receive thread started\n"); + + while (m_threadRunning.load()) { + // 检查串口是否打开 + if (!m_pSerialPort || !m_pSerialPort->isOpen()) { + // 串口未打开,短暂休眠后继续检查 + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + continue; + } + + try { + // 使用waitForReadyRead进行阻塞式等待,超时时间100ms + if (m_pSerialPort->waitForReadyRead(100)) { + // 有数据可读 + qint64 bytesAvailable = m_pSerialPort->bytesAvailable(); + if (bytesAvailable > 0) { + LOG_INFO("[Thread] Bytes available to read: %lld\n", bytesAvailable); + + // 读取所有可用数据 + QByteArray data = m_pSerialPort->readAll(); + if (!data.isEmpty()) { + QString receivedData = QString::fromUtf8(data); + + LOG_INFO("[Thread] Actually read %d bytes from serial port\n", data.size()); + LOG_INFO("[Thread] Received [%d bytes]: \"%s\"\n", data.size(), receivedData.toStdString().c_str()); + + // 使用互斥锁保护缓冲区 + { + std::lock_guard lock(m_bufferMutex); + m_receiveBuffer += receivedData; + } + + // 处理接收到的数据 + ProcessReceivedData(); + } + } + } + } catch (const std::exception& e) { + LOG_ERROR("[Thread] Exception in receive thread: %s\n", e.what()); + } catch (...) { + LOG_ERROR("[Thread] Unknown exception in receive thread\n"); + } + } + + LOG_INFO("Serial receive thread finished\n"); +} + +void SerialProtocol::ProcessReceivedData() +{ + std::lock_guard lock(m_bufferMutex); + + // 查找完整的协议帧 + int startIndex = -1; + int endIndex = -1; + + while ((startIndex = m_receiveBuffer.indexOf(PROTOCOL_START)) != -1) { + endIndex = m_receiveBuffer.indexOf(PROTOCOL_END, startIndex); + if (endIndex != -1) { + // 提取完整的协议帧 + QString protocolFrame = m_receiveBuffer.mid(startIndex, endIndex - startIndex + 1); + + LOG_INFO("[Thread] Found complete protocol frame: %s\n", protocolFrame.toStdString().c_str()); + + // 解析协议数据 + ParseProtocolData(protocolFrame); + + // 移除已处理的数据 + m_receiveBuffer.remove(0, endIndex + 1); + } else { + // 没有找到结束标识,等待更多数据 + break; + } + } + + // 如果缓冲区太大,清空一部分(防止内存溢出) + if (m_receiveBuffer.length() > 1024) { + LOG_WARNING("[Thread] Receive buffer too large (%d bytes), clearing\n", m_receiveBuffer.length()); + m_receiveBuffer.clear(); + } +} diff --git a/GrabBagApp/Version.h b/GrabBagApp/Version.h index 234c5c0..acefee7 100644 --- a/GrabBagApp/Version.h +++ b/GrabBagApp/Version.h @@ -3,8 +3,8 @@ #define GRABBAG_VERSION_STRING "1.0.1" -#define GRABBAG_BUILD_STRING "1" -#define GRABBAG_FULL_VERSION_STRING "V1.0.1_1" +#define GRABBAG_BUILD_STRING "3" +#define GRABBAG_FULL_VERSION_STRING "V1.0.1_3" // 获取版本信息的便捷函数 inline const char* GetGrabBagVersion() { diff --git a/GrabBagApp/Version.md b/GrabBagApp/Version.md deleted file mode 100644 index 706bff5..0000000 --- a/GrabBagApp/Version.md +++ /dev/null @@ -1,8 +0,0 @@ -# 1.0.1 -``` -1. 增加串口通信 -``` -# 1.0.0 -``` -初始版本 -``` \ No newline at end of file diff --git a/GrabBagPrj/Version/Version.md b/GrabBagPrj/Version/Version.md index e9898f0..ba8b60d 100644 --- a/GrabBagPrj/Version/Version.md +++ b/GrabBagPrj/Version/Version.md @@ -1,3 +1,7 @@ +# 1.0.1 2025-07-14 +1. 增加串口通信 + + # 1.0.0.7 2025-06-29 1. 增加双相机界面: 通过配置文件config.xml 配置双相机参数,如果没有没有配置,搜索相机进行打开 @@ -12,22 +16,22 @@ 3. 修改log, config的目录。 存储在用户目录下->应用名下 4. 增加应用只能启动一个 5. 手眼标定矩阵 -``` -[clib] -clib_0=1 -clib_1=0 -clib_2=0 -clib_3=0 -clib_4=0 -clib_5=1 -clib_6=0 -clib_7=0 -clib_8=0 -clib_9=0 -clib_10=1 -clib_11=0 -clib_12=0 -clib_13=0 -clib_14=0 -clib_15=1 -``` \ No newline at end of file + ``` + [clib] + clib_0=1 + clib_1=0 + clib_2=0 + clib_3=0 + clib_4=0 + clib_5=1 + clib_6=0 + clib_7=0 + clib_8=0 + clib_9=0 + clib_10=1 + clib_11=0 + clib_12=0 + clib_13=0 + clib_14=0 + clib_15=1 + ``` diff --git a/Module/ShareMem/Inc/IVrShareMem.h b/Module/ShareMem/Inc/IVrShareMem.h index 737ab0b..b7acb57 100644 --- a/Module/ShareMem/Inc/IVrShareMem.h +++ b/Module/ShareMem/Inc/IVrShareMem.h @@ -8,7 +8,7 @@ * @brief 共享内存接口类 * 提供跨平台的共享内存创建、映射、读写操作 */ -class VRSHAREMEM_EXPORT IVrShareMem +class IVrShareMem { public: virtual ~IVrShareMem() = default; @@ -94,12 +94,12 @@ public: * @brief 创建共享内存实例 * @return 共享内存实例指针 */ -VRSHAREMEM_EXPORT IVrShareMem* CreateShareMemInstance(); +IVrShareMem* CreateShareMemInstance(); /** * @brief 销毁共享内存实例 * @param instance 实例指针 */ -VRSHAREMEM_EXPORT void DestroyShareMemInstance(IVrShareMem* instance); +void DestroyShareMemInstance(IVrShareMem* instance); -#endif // IVRSHAREMEM_H \ No newline at end of file +#endif // IVRSHAREMEM_H diff --git a/Module/ShareMem/ShareMem.pro b/Module/ShareMem/ShareMem.pro index effc65c..44edd14 100644 --- a/Module/ShareMem/ShareMem.pro +++ b/Module/ShareMem/ShareMem.pro @@ -1,6 +1,7 @@ #CONFIG -= qt TEMPLATE = lib +CONFIG += staticlib DEFINES += VRSHAREMEM_LIBRARY CONFIG += c++11 @@ -34,4 +35,4 @@ unix { # Windows specific libraries win32 { LIBS += -luser32 -lkernel32 -} \ No newline at end of file +} diff --git a/Module/ShareMem/Src/VrShareMem.cpp b/Module/ShareMem/Src/VrShareMem.cpp index eae92eb..22151db 100644 --- a/Module/ShareMem/Src/VrShareMem.cpp +++ b/Module/ShareMem/Src/VrShareMem.cpp @@ -300,12 +300,12 @@ bool VrShareMem::CheckBounds(size_t offset, size_t size) const } // 导出函数实现 -VRSHAREMEM_EXPORT IVrShareMem* CreateShareMemInstance() +IVrShareMem* CreateShareMemInstance() { return new VrShareMem(); } -VRSHAREMEM_EXPORT void DestroyShareMemInstance(IVrShareMem* instance) +void DestroyShareMemInstance(IVrShareMem* instance) { delete instance; -} \ No newline at end of file +} diff --git a/VrConfig/Inc/IVrConfig.h b/VrConfig/Inc/IVrConfig.h index cabd890..12021fb 100644 --- a/VrConfig/Inc/IVrConfig.h +++ b/VrConfig/Inc/IVrConfig.h @@ -17,7 +17,11 @@ struct DeviceInfo */ struct SerialConfig { +#ifdef _WIN32 + std::string portName = "COM8"; // 串口名称 +#else std::string portName = "/dev/ttyS3"; // 串口名称 +#endif int baudRate = 115200; // 波特率 int dataBits = 8; // 数据位 int stopBits = 1; // 停止位