diff --git a/App/App.pro b/App/App.pro index 3dcb78fb..2f3f9a57 100644 --- a/App/App.pro +++ b/App/App.pro @@ -60,9 +60,9 @@ isEmpty(TARGET_APP) { } else:equals(TARGET_APP, "StatorPosition") { SUBDIRS += StatorPosition/StatorPosition.pro } else:equals(TARGET_APP, "WorkpieceTapered") { - SUBDIRS += StatorWorkpiecePosition/WorkpieceTapered.pro + SUBDIRS += StatorWorkpiecePosition/StatorWorkpiecePosition.pro } else:equals(TARGET_APP, "WorkpieceRotor") { - SUBDIRS += StatorWorkpiecePosition/WorkpieceRotor.pro + SUBDIRS += StatorWorkpiecePosition/StatorWorkpiecePosition.pro } else:equals(TARGET_APP, "HoleDetection") { SUBDIRS += HoleDetection/HoleDetection.pro } else:equals(TARGET_APP, "HolePitPosition") { diff --git a/App/BeltTearing/BeltTearingServer/BeltTearingPresenter.cpp b/App/BeltTearing/BeltTearingServer/BeltTearingPresenter.cpp index 24c4cc5d..30900492 100644 --- a/App/BeltTearing/BeltTearingServer/BeltTearingPresenter.cpp +++ b/App/BeltTearing/BeltTearingServer/BeltTearingPresenter.cpp @@ -376,7 +376,7 @@ void BeltTearingPresenter::sendSimulationData() SendDetectionResultToRobot(alarmResults); if (m_tearingProtocol) { - m_tearingProtocol->sendDetectResult(alarmResults, QImage()); + m_tearingProtocol->sendDetectResult(filteredResults, QImage(), !alarmResults.empty()); m_tearingProtocol->sendRealtimeResult(filteredResults, QImage()); } } @@ -1896,7 +1896,7 @@ int BeltTearingPresenter::_DetectTask() // 使用TearingTcpProtocol发送DETECT_RESULT消息(新协议) if (m_tearingProtocol) { - m_tearingProtocol->sendDetectResult(alarmTearings, image); + m_tearingProtocol->sendDetectResult(filteredTearings, image, !alarmTearings.empty()); } // 发送实时检测结果(新协议) diff --git a/App/BeltTearing/BeltTearingServer/TearingTcpProtocol.cpp b/App/BeltTearing/BeltTearingServer/TearingTcpProtocol.cpp index 9a84e70e..72c2c8c2 100644 --- a/App/BeltTearing/BeltTearingServer/TearingTcpProtocol.cpp +++ b/App/BeltTearing/BeltTearingServer/TearingTcpProtocol.cpp @@ -3,6 +3,7 @@ #include "Version.h" #include #include +#include #include TearingTcpProtocol::TearingTcpProtocol(QObject *parent) @@ -16,6 +17,7 @@ TearingTcpProtocol::TearingTcpProtocol(QObject *parent) , m_historyMaxLength(0) , m_historyMaxId(0) , m_historyMaxWidth(0) + , m_hasLastDetectResult(false) { // 连接心跳定时器 connect(m_heartbeatTimer, &QTimer::timeout, this, &TearingTcpProtocol::onHeartbeatTimeout); @@ -97,18 +99,20 @@ void TearingTcpProtocol::stop() LOG_INFO("TearingTcpProtocol stopped\n"); } -void TearingTcpProtocol::sendDetectResult(const std::vector& results, const QImage& visImage) +void TearingTcpProtocol::sendDetectResult(const std::vector& results, + const QImage& visImage, bool alarm) { if (!m_tcpServer) { return; } + std::lock_guard sendLock(m_detectSendMutex); // 计算撕裂个数和本次检测的最大撕裂长度 - int count = static_cast(results.size()); int currentMaxLength = 0; int currentMaxId = 0; int currentMaxWidth = 0; - QJsonArray tearsArray; + std::vector currentTearObjects; + currentTearObjects.reserve(results.size()); for (const auto& result : results) { int tearLength = static_cast(std::round(std::abs(result.roi.right - result.roi.left))); @@ -125,12 +129,13 @@ void TearingTcpProtocol::sendDetectResult(const std::vector tearObj["length"] = tearLength; tearObj["width"] = tearWidth; tearObj["depth"] = result.tearDepth; - tearsArray.append(tearObj); + currentTearObjects.push_back(tearObj); } int historyMaxLength = 0; int historyMaxId = 0; int historyMaxWidth = 0; + QJsonArray tearsArray; { std::lock_guard lock(m_historyMutex); // 更新历史最大值(从开流开始的最大值) @@ -142,6 +147,55 @@ void TearingTcpProtocol::sendDetectResult(const std::vector historyMaxLength = m_historyMaxLength; historyMaxId = m_historyMaxId; historyMaxWidth = m_historyMaxWidth; + + for (const auto& tearObj : currentTearObjects) { + m_historyTears[tearObj.value("id").toInt()] = tearObj; + } + std::vector historyTearObjects; + historyTearObjects.reserve(m_historyTears.size()); + for (auto it = m_historyTears.cbegin(); it != m_historyTears.cend(); ++it) { + historyTearObjects.push_back(it.value()); + } + std::sort(historyTearObjects.begin(), historyTearObjects.end(), [](const QJsonObject& lhs, const QJsonObject& rhs) { + const int lhsLength = lhs.value("length").toInt(); + const int rhsLength = rhs.value("length").toInt(); + if (lhsLength != rhsLength) { + return lhsLength > rhsLength; + } + return lhs.value("id").toInt() < rhs.value("id").toInt(); + }); + if (historyTearObjects.size() > 5) { + historyTearObjects.resize(5); + } + m_historyTears.clear(); + for (const auto& tearObj : historyTearObjects) { + m_historyTears.insert(tearObj.value("id").toInt(), tearObj); + tearsArray.append(tearObj); + } + } + + const int count = tearsArray.size(); + + // Timestamp and image are deliberately excluded: only tearing information + // and the accumulated maximum values trigger a DETECT_RESULT report. + QJsonObject signatureObj; + signatureObj["count"] = count; + signatureObj["maxLength"] = historyMaxLength; + signatureObj["maxWidth"] = historyMaxWidth; + signatureObj["maxId"] = historyMaxId; + signatureObj["tears"] = tearsArray; + const QByteArray signature = QJsonDocument(signatureObj).toJson(QJsonDocument::Compact); + + { + std::lock_guard lock(m_historyMutex); + const bool hasCurrentInfo = count > 0 || historyMaxLength > 0 || + historyMaxWidth > 0 || historyMaxId > 0; + if (!hasCurrentInfo && !m_hasLastDetectResult) { + return; + } + if (m_hasLastDetectResult && signature == m_lastDetectSignature) { + return; + } } // 构造JSON消息(使用历史最大值) @@ -149,7 +203,7 @@ void TearingTcpProtocol::sendDetectResult(const std::vector addProtocolVersion(jsonObj); jsonObj["msgType"] = "DETECT_RESULT"; jsonObj["timestamp"] = QDateTime::currentMSecsSinceEpoch(); - jsonObj["alarm"] = count > 0; + jsonObj["alarm"] = alarm; jsonObj["count"] = count; jsonObj["max"] = historyMaxLength; // 保留1.4版本字段 jsonObj["maxLength"] = historyMaxLength; @@ -171,10 +225,13 @@ void TearingTcpProtocol::sendDetectResult(const std::vector // 构造数据帧 QByteArray frame = buildFrame(jsonData); - // DETECT_RESULT is the baseline result stream and is always broadcast to - // every connected client, independent of SET_REALTIME. + // DETECT_RESULT is the baseline result stream and is broadcast to every + // connected client when its tearing summary changes, independent of SET_REALTIME. bool success = m_tcpServer->SendAllData(frame.constData(), frame.size()); if (success) { + std::lock_guard lock(m_historyMutex); + m_lastDetectSignature = signature; + m_hasLastDetectResult = true; LOG_DEBUG("Sent DETECT_RESULT to all clients, count=%d, historyMax=%d, historyMaxId=%d\n", count, historyMaxLength, historyMaxId); } else { @@ -360,6 +417,9 @@ void TearingTcpProtocol::clearHistoryMaxData() m_historyMaxLength = 0; m_historyMaxId = 0; m_historyMaxWidth = 0; + m_historyTears.clear(); + m_lastDetectSignature.clear(); + m_hasLastDetectResult = false; } LOG_INFO("Cleared history max data (开流清除)\n"); } diff --git a/App/BeltTearing/BeltTearingServer/TearingTcpProtocol.h b/App/BeltTearing/BeltTearingServer/TearingTcpProtocol.h index a9f910a2..c189c4c0 100644 --- a/App/BeltTearing/BeltTearingServer/TearingTcpProtocol.h +++ b/App/BeltTearing/BeltTearingServer/TearingTcpProtocol.h @@ -94,7 +94,8 @@ public: * @param visImage 可视化图像 * @note 发送的JSON消息包含:count(撕裂个数)、max(最大撕裂长度)、maxId(最大撕裂ID)、visimg(Base64图像) */ - void sendDetectResult(const std::vector& results, const QImage& visImage); + void sendDetectResult(const std::vector& results, + const QImage& visImage, bool alarm = true); /** * @brief 发送实时检测结果 @@ -293,6 +294,10 @@ private: int m_historyMaxLength; // 历史最大撕裂长度 int m_historyMaxId; // 历史最大撕裂对应的ID int m_historyMaxWidth; // 历史最大撕裂对应的宽度 + mutable std::mutex m_detectSendMutex; + QMap m_historyTears; + QByteArray m_lastDetectSignature; + bool m_hasLastDetectResult; }; #endif // TEARINGTCPPROTOCOL_H diff --git a/App/BeltTearing/BeltTearingServer/Version.h b/App/BeltTearing/BeltTearingServer/Version.h index 9a0c461e..15a9866d 100644 --- a/App/BeltTearing/BeltTearingServer/Version.h +++ b/App/BeltTearing/BeltTearingServer/Version.h @@ -1,7 +1,7 @@ #ifndef VERSION_H #define VERSION_H -#define BELTTEARING_VERSION_STRING "2.1.0" +#define BELTTEARING_VERSION_STRING "2.1.1" #define BELTTEARING_VERSION_BUILD "1" #define BELTTEARING_PRODUCT_NAME "BeltTearingServer" #define BELTTEARING_COMPANY_NAME "VisionRobot" diff --git a/App/BeltTearing/BeltTearingServer/Version.md b/App/BeltTearing/BeltTearingServer/Version.md index 7e0bf276..e42fb13a 100644 --- a/App/BeltTearing/BeltTearingServer/Version.md +++ b/App/BeltTearing/BeltTearingServer/Version.md @@ -1,6 +1,11 @@ +# 2.1.1 +## build_1 2026-08-27 +1. 协议汇总结果上报bug修复 + # 2.1.0 ## build_1 2026-08-10 -1. +1. 协议修改 +2. 生成图像使用伪彩 # 2.0.9 ## build_1 2026-07-27 diff --git a/App/BeltTearing/Doc/撕裂TCP通信协议.md b/App/BeltTearing/Doc/撕裂TCP通信协议.md index 4e3e7ede..496fd758 100644 --- a/App/BeltTearing/Doc/撕裂TCP通信协议.md +++ b/App/BeltTearing/Doc/撕裂TCP通信协议.md @@ -9,7 +9,7 @@ | 版本 | 日期 | 修改内容 | |------|------------|------------------| -| 1.6 | 2026-08-10 | 增加激光线点云二进制实时发送,以及可配置的抽线、抽点功能;
算法和成像仍使用完整点云;
DETECT_RESULT 始终广播,REALTIME_RESULT 由 SET_REALTIME 单独开启 | +| 1.6 | 2026-08-10 | 增加激光线点云二进制实时发送,以及可配置的抽线、抽点功能;
算法和成像仍使用完整点云;
DETECT_RESULT 始终开启但仅在历史汇总变化时广播,REALTIME_RESULT 由 SET_REALTIME 单独开启 | | 1.5 | 2026-08-05 | 增加协议版本字段、完整报警数据、报警复位、重新检测、配置读写和协议能力查询;保留1.4字段兼容旧客户端 | | 1.4 | 2026-02-21 | 增加CMD汇总表格(实时消息关系以1.6定义为准) | | 1.3 | 2026-02-12 | 增加实时传输检测结果功能(SET_REALTIME/REALTIME_RESULT) | @@ -43,7 +43,7 @@ | msgType | 方向 | 说明 | 详细章节 | |---------|------|------|----------| -| `DETECT_RESULT` | 服务器 → 客户端 | 基础撕裂检测结果上报(历史最大值),始终发送给所有客户端 | 4.1 | +| `DETECT_RESULT` | 服务器 → 客户端 | 基础撕裂检测结果上报(历史最大值),结果发生变化时发送给所有客户端 | 4.1 | | `REALTIME_RESULT` | 服务器 → 客户端 | 实时检测结果上报(所有撕裂详细数据),
仅发送给**已开启**实时传输的客户端 | 4.2 | | `SET_SPEED` | 客户端 → 服务器 | 设置皮带速度(mm/s) | 5.1 | | `SET_CONTROL` | 客户端 → 服务器 | 启动/停止检测 | 5.2 | @@ -117,7 +117,7 @@ **消息类型**: `DETECT_RESULT` -服务器始终向所有已连接客户端发送本消息,不受 `SET_REALTIME` 状态影响;无撕裂时 `count` 为0且 `tears` 为空数组。 +服务器始终开启本消息上报逻辑,向所有已连接客户端发送;不受 `SET_REALTIME` 状态影响。仅当当前汇总中的撕裂信息或历史最大值发生变化时发送,重复结果不连续发送;无撕裂时 `count` 为0且 `tears` 为空数组。 **JSON格式**: ```json { @@ -144,13 +144,13 @@ | msgType | string | 是 | 固定值 `DETECT_RESULT` | | timestamp | int64 | 是 | 检测时间戳(毫秒) | | protocolVersion | string | 是 | 当前协议版本 `1.6` | -| alarm | bool | 是 | 本次消息是否包含未确认报警 | +| alarm | bool | 是 | 本次检测是否存在尚未被 `RESET_ALARM` 确认的撕裂 | | count | int | 是 | 撕裂个数 | | max | int | 是 | 历史最大X方向长度,兼容1.4客户端 | | maxLength | int | 是 | 历史最大X方向长度(毫米) | | maxWidth | int | 是 | 历史最大长度对应撕裂的Y方向宽度(毫米) | | maxId | int | 是 | 最大撕裂对应的撕裂ID(唯一标识符) | -| tears | array | 是 | 当前未确认的完整撕裂列表,字段同4.2 | +| tears | array | 是 | 从本次开流或最近一次 `RESET_ALARM` 起累计的撕裂结果;最多保留长度最大的5条,字段同4.2 | | visimg | string | 是 | Base64编码的检测结果图片(JPEG格式) | **示例**: @@ -230,7 +230,7 @@ - 实时传输功能需要客户端通过 `SET_REALTIME` 命令开启 - 开启后,服务器每次完成一帧检测即上报一次结果 - 关闭实时传输后,服务器停止上报 `REALTIME_RESULT` 消息 -- `DETECT_RESULT` 始终发送;开启实时传输的客户端会在此基础上额外收到 `REALTIME_RESULT` +- `DETECT_RESULT` 始终开启上报逻辑,但仅在撕裂信息或汇总值变化时发送;开启实时传输的客户端会在此基础上额外收到 `REALTIME_RESULT` - 当 count 为 0 时,tears 为空数组 `[]` **示例**(无撕裂时): @@ -521,9 +521,11 @@ ## 10. 1.5/1.6 增强命令 -### 10.1 报警复位与重新检测 - -```json +### 10.1 报警复位与重新检测 + +`RESET_ALARM` 的“确认”由服务端执行:客户端发送命令后,服务端将当时仍存在的撕裂 ID 加入已确认集合,并据此抑制后续同 ID 的报警状态;该 ID 消失后再次出现,才可重新报警。 + +```json {"msgType":"RESET_ALARM","protocolVersion":"1.6","timestamp":1699776000000} ```