From 5946037da7056ebc1a5cfcb3286ac9897e0d292c Mon Sep 17 00:00:00 2001 From: yiyi Date: Sun, 14 Sep 2025 14:51:38 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8B=86=E5=8C=85=E5=A2=9E=E5=8A=A0tcp?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App/BeltTearing/BeltTearing.pro | 12 + .../BeltTearingApp}/BeltTearingApp.pro | 20 +- .../BeltTearingApp}/IStatusUpdate.h | 0 .../Presenter/Inc/BeltTearingPresenter.h | 0 .../Presenter/Inc/PathManager.h | 0 .../Presenter/Src/BeltTearingPresenter.cpp | 0 .../Presenter/Src/PathManager.cpp | 0 .../BeltTearingApp}/dialognetconfig.cpp | 0 .../BeltTearingApp}/dialognetconfig.h | 0 .../BeltTearingApp}/dialognetconfig.ui | 0 .../BeltTearing/BeltTearingApp}/main.cpp | 0 .../BeltTearingApp}/mainwindow.cpp | 0 .../BeltTearing/BeltTearingApp}/mainwindow.h | 0 .../BeltTearing/BeltTearingApp}/mainwindow.ui | 0 .../BeltTearingApp}/models/ImageInfoModel.cpp | 0 .../BeltTearingApp}/models/ImageInfoModel.h | 0 .../resource/camera_offline.png | Bin .../resource/camera_online.png | Bin .../BeltTearingApp}/resource/close.png | Bin .../BeltTearingApp}/resource/config_algo.png | Bin .../resource/config_algo_s.png | Bin .../resource/config_camera.png | Bin .../resource/config_camera_level.png | Bin .../resource/config_camera_level_s.png | Bin .../resource/config_camera_s.png | Bin .../resource/config_data_test.png | Bin .../resource/config_data_test.svg | 0 .../resource/config_data_test_s.png | Bin .../BeltTearingApp}/resource/config_model.png | Bin .../resource/dialog_cancel.png | Bin .../BeltTearingApp}/resource/dialog_ok.png | Bin .../BeltTearingApp}/resource/hide.png | Bin .../BeltTearingApp}/resource/logo.ico | Bin .../BeltTearingApp}/resource/logo.png | Bin .../BeltTearingApp}/resource/result_icon.png | Bin .../resource/robot_offline.png | Bin .../BeltTearingApp}/resource/robot_online.png | Bin .../BeltTearingApp}/resource/start.png | Bin .../BeltTearingApp}/resource/stop.png | Bin .../BeltTearing/BeltTearingApp}/resources.qrc | 0 .../BeltTearingApp}/widgets/ClickableFrame.h | 0 .../widgets/DeviceStatusWidget.cpp | 0 .../widgets/DeviceStatusWidget.h | 0 .../widgets/ImageGridWidget.cpp | 0 .../BeltTearingApp}/widgets/ImageGridWidget.h | 0 .../widgets/ImageGridWithTableWidget.cpp | 0 .../widgets/ImageGridWithTableWidget.h | 0 .../widgets/ImageTileWidget.cpp | 0 .../BeltTearingApp}/widgets/ImageTileWidget.h | 0 .../widgets/StyledMessageBox.cpp | 0 .../widgets/StyledMessageBox.h | 0 .../widgets/TearingDataTableWidget.cpp | 0 .../widgets/TearingDataTableWidget.h | 0 .../BeltTearingConfig}/BeltTearingConfig.pro | 8 +- .../Inc/IVrBeltTearingConfig.h | 0 .../Inc/VrBeltTearingConfig.h | 0 .../Src/VrBeltTearingConfig.cpp | 0 .../BeltTearingConfig}/config/config.xml | 0 .../BeltTearingServer}/BeltTearingAlgo.cpp | 0 .../BeltTearingServer}/BeltTearingAlgo.h | 0 .../BeltTearingPresenter.cpp | 0 .../BeltTearingServer}/BeltTearingPresenter.h | 0 .../BeltTearingServer}/BeltTearingServer.pro | 38 +- .../BeltTearingServer}/PathManager.cpp | 0 .../BeltTearingServer}/PathManager.h | 0 .../BeltTearingServer}/SG_baseAlgo_Export.h | 0 .../BeltTearingServer}/SG_baseDataType.h | 0 .../BeltTearingServer}/SG_errCode.h | 0 .../BeltTearing/BeltTearingServer}/Version.h | 0 .../beltTearingDetection.cpp | 0 .../beltTearingDetection_Export.h | 0 .../BeltTearing/BeltTearingServer}/main.cpp | 0 ...odbusTCP_编织袋拆垛项目相机端通信协议.docx | Bin .../GrabBag/Doc}/RS485_拆垛机协议.docx | Bin App/GrabBag/Doc/拆垛机tcpip协议.docx | Bin 0 -> 13313 bytes App/GrabBag/GrabBag.pro | 5 + App/GrabBag/GrabBagApp/GrabBagApp.pro | 168 ++ .../GrabBag/GrabBagApp}/IYGrabBagStatus.h | 0 .../GrabBagApp}/Presenter/Inc/ConfigManager.h | 0 .../Presenter/Inc/DetectPresenter.h | 0 .../Presenter/Inc/GrabBagPresenter.h | 29 +- .../Presenter/Inc/ProtocolCommon.h | 0 .../GrabBagApp}/Presenter/Inc/RobotProtocol.h | 0 .../Presenter/Inc/SerialProtocol.h | 0 .../Presenter/Src/ConfigManager.cpp | 0 .../Presenter/Src/DetectPresenter.cpp | 0 .../Presenter/Src/GrabBagPresenter.cpp | 158 +- .../Presenter/Src/RobotProtocol.cpp | 0 .../Presenter/Src/SerialProtocol.cpp | 0 .../GrabBagApp}/Utils/Inc/LaserDataLoader.h | 0 .../GrabBagApp}/Utils/Inc/PathManager.h | 0 .../Utils/Inc/PointCloudImageUtils.h | 0 .../GrabBagApp}/Utils/Src/LaserDataLoader.cpp | 0 .../GrabBagApp}/Utils/Src/PathManager.cpp | 0 .../Utils/Src/PointCloudImageUtils.cpp | 0 .../GrabBag/GrabBagApp}/Version.h | 6 +- .../GrabBag/GrabBagApp}/Version.md | 7 + .../GrabBag/GrabBagApp}/devstatus.cpp | 0 .../GrabBag/GrabBagApp}/devstatus.h | 0 .../GrabBag/GrabBagApp}/devstatus.ui | 0 .../GrabBag/GrabBagApp}/dialogcamera.cpp | 0 .../GrabBag/GrabBagApp}/dialogcamera.h | 0 .../GrabBag/GrabBagApp}/dialogcamera.ui | 0 .../GrabBag/GrabBagApp}/dialogcameralevel.cpp | 0 .../GrabBag/GrabBagApp}/dialogcameralevel.h | 0 .../GrabBag/GrabBagApp}/dialogcameralevel.ui | 0 .../GrabBag/GrabBagApp}/dialogconfig.cpp | 0 .../GrabBag/GrabBagApp}/dialogconfig.h | 0 .../GrabBag/GrabBagApp}/dialogconfig.ui | 0 .../GrabBag/GrabBagApp}/main.cpp | 0 .../GrabBag/GrabBagApp}/mainwindow.cpp | 0 .../GrabBag/GrabBagApp}/mainwindow.h | 0 .../GrabBag/GrabBagApp}/mainwindow.ui | 0 .../GrabBagApp}/resource/camera_offline.png | Bin .../GrabBagApp}/resource/camera_online.png | Bin .../GrabBag/GrabBagApp}/resource/close.png | Bin .../GrabBagApp}/resource/config_algo.png | Bin .../GrabBagApp}/resource/config_algo_s.png | Bin .../GrabBagApp}/resource/config_camera.png | Bin .../resource/config_camera_level.png | Bin .../resource/config_camera_level_s.png | Bin .../GrabBagApp}/resource/config_camera_s.png | Bin .../GrabBagApp}/resource/config_data_test.png | Bin .../GrabBagApp}/resource/config_data_test.svg | 0 .../resource/config_data_test_s.png | Bin .../GrabBagApp}/resource/config_model.png | Bin .../GrabBagApp}/resource/dialog_cancel.png | Bin .../GrabBagApp}/resource/dialog_ok.png | Bin .../GrabBag/GrabBagApp}/resource/hide.png | Bin .../GrabBag/GrabBagApp}/resource/logo.ico | Bin .../GrabBag/GrabBagApp}/resource/logo.png | Bin .../GrabBagApp}/resource/result_icon.png | Bin .../GrabBagApp}/resource/robot_offline.png | Bin .../GrabBagApp}/resource/robot_online.png | Bin .../GrabBag/GrabBagApp}/resource/start.png | Bin .../GrabBag/GrabBagApp}/resource/stop.png | Bin .../GrabBag/GrabBagApp}/resources.qrc | 0 .../GrabBag/GrabBagApp}/resultitem.cpp | 0 .../GrabBag/GrabBagApp}/resultitem.h | 0 .../GrabBag/GrabBagApp}/resultitem.ui | 0 .../GrabBag/GrabBagConfig}/CMakeLists.txt | 0 .../GrabBag/GrabBagConfig}/GrabBagConfig.pro | 12 +- .../GrabBag/GrabBagConfig}/Inc/IVrConfig.h | 12 +- .../GrabBag/GrabBagConfig}/Src/VrConfig.cpp | 27 +- .../GrabBag/GrabBagConfig}/_Inc/VrConfig.h | 0 .../config/EyeHandCalibMatrixInfo.ini | 0 .../GrabBag/GrabBagConfig}/config/config.xml | 6 + .../GrabBagConfigCmd}/ConfigCmdParser.cpp | 0 .../GrabBagConfigCmd}/ConfigCmdParser.h | 0 .../GrabBagConfigCmd}/GrabBagConfigCmd.pro | 0 .../GrabBag/GrabBagConfigCmd}/main.cpp | 0 .../GrabBag/GrabBagConfigCmd}/使用示例.md | 0 App/LapWeld/LapWeld.pro | 4 + App/LapWeld/LapWeldApp/IYLapWeldStatus.h | 91 ++ .../LapWeld/LapWeldApp/LapWeldApp.pro | 85 +- .../LapWeldApp/Presenter/Inc/ConfigManager.h | 157 ++ .../Presenter/Inc/DetectPresenter.h | 48 + .../Presenter/Inc/LapWeldPresenter.h | 177 +++ .../LapWeldApp/Presenter/Inc/ProtocolCommon.h | 72 + .../LapWeldApp/Presenter/Inc/RobotProtocol.h | 140 ++ .../LapWeldApp/Presenter/Inc/SerialProtocol.h | 165 ++ .../Presenter/Src/ConfigManager.cpp | 773 ++++++++++ .../Presenter/Src/DetectPresenter.cpp | 185 +++ .../Presenter/Src/LapWeldPresenter.cpp | 1372 +++++++++++++++++ .../Presenter/Src/RobotProtocol.cpp | 254 +++ .../Presenter/Src/SerialProtocol.cpp | 550 +++++++ .../LapWeldApp/Utils/Inc/LaserDataLoader.h | 66 + .../LapWeldApp/Utils/Inc/PathManager.h | 57 + .../Utils/Inc/PointCloudImageUtils.h | 55 + .../LapWeldApp/Utils/Src/LaserDataLoader.cpp | 432 ++++++ .../LapWeldApp/Utils/Src/PathManager.cpp | 66 + .../Utils/Src/PointCloudImageUtils.cpp | 517 +++++++ App/LapWeld/LapWeldApp/Version.h | 23 + App/LapWeld/LapWeldApp/devstatus.cpp | 329 ++++ App/LapWeld/LapWeldApp/devstatus.h | 84 + App/LapWeld/LapWeldApp/devstatus.ui | 181 +++ App/LapWeld/LapWeldApp/dialogcamera.cpp | 199 +++ App/LapWeld/LapWeldApp/dialogcamera.h | 45 + App/LapWeld/LapWeldApp/dialogcamera.ui | 394 +++++ App/LapWeld/LapWeldApp/dialogcameralevel.cpp | 839 ++++++++++ App/LapWeld/LapWeldApp/dialogcameralevel.h | 106 ++ App/LapWeld/LapWeldApp/dialogcameralevel.ui | 155 ++ App/LapWeld/LapWeldApp/main.cpp | 66 + App/LapWeld/LapWeldApp/mainwindow.cpp | 916 +++++++++++ App/LapWeld/LapWeldApp/mainwindow.h | 139 ++ App/LapWeld/LapWeldApp/mainwindow.ui | 387 +++++ .../LapWeldApp/resource/camera_offline.png | Bin 0 -> 1239 bytes .../LapWeldApp/resource/camera_online.png | Bin 0 -> 1240 bytes App/LapWeld/LapWeldApp/resource/close.png | Bin 0 -> 1693 bytes .../LapWeldApp/resource/config_algo.png | Bin 0 -> 4585 bytes .../LapWeldApp/resource/config_algo_s.png | Bin 0 -> 4388 bytes .../LapWeldApp/resource/config_camera.png | Bin 0 -> 4780 bytes .../resource/config_camera_level.png | Bin 0 -> 4370 bytes .../resource/config_camera_level_s.png | Bin 0 -> 4348 bytes .../LapWeldApp/resource/config_camera_s.png | Bin 0 -> 4672 bytes .../LapWeldApp/resource/config_data_test.png | Bin 0 -> 5672 bytes .../LapWeldApp/resource/config_data_test.svg | 1 + .../resource/config_data_test_s.png | Bin 0 -> 5541 bytes .../LapWeldApp/resource/config_model.png | Bin 0 -> 3500 bytes .../LapWeldApp/resource/dialog_cancel.png | Bin 0 -> 1968 bytes App/LapWeld/LapWeldApp/resource/dialog_ok.png | Bin 0 -> 1560 bytes App/LapWeld/LapWeldApp/resource/hide.png | Bin 0 -> 312 bytes App/LapWeld/LapWeldApp/resource/logo.ico | Bin 0 -> 38078 bytes App/LapWeld/LapWeldApp/resource/logo.png | Bin 0 -> 7080 bytes .../LapWeldApp/resource/result_icon.png | Bin 0 -> 1188 bytes .../LapWeldApp/resource/robot_offline.png | Bin 0 -> 1322 bytes .../LapWeldApp/resource/robot_online.png | Bin 0 -> 1364 bytes App/LapWeld/LapWeldApp/resource/start.png | Bin 0 -> 2131 bytes App/LapWeld/LapWeldApp/resource/stop.png | Bin 0 -> 1535 bytes App/LapWeld/LapWeldApp/resources.qrc | 26 + App/LapWeld/LapWeldApp/resultitem.cpp | 44 + App/LapWeld/LapWeldApp/resultitem.h | 29 + App/LapWeld/LapWeldApp/resultitem.ui | 285 ++++ App/LapWeld/LapWeldConfig/Inc/IVrConfig.h | 259 ++++ App/LapWeld/LapWeldConfig/LapWeldConfig.pro | 39 + App/LapWeld/LapWeldConfig/Src/VrConfig.cpp | 455 ++++++ App/LapWeld/LapWeldConfig/_Inc/VrConfig.h | 49 + .../config/EyeHandCalibMatrixInfo.ini | 26 + App/LapWeld/LapWeldConfig/config/config.xml | 78 + GrabBagPrj/GrabBagPrj.pro | 18 +- GrabBagPrj/pkg_grabbagapp.sh | 28 +- VrNets/VrTcpServer.pro | 31 +- VrNets/tcpClient/Inc/IVrTcpClient.h | 55 - VrNets/tcpClient/Inc/VrTcpClient.h | 39 - VrNets/tcpClient/Src/VrTcpClient.cpp | 150 -- VrNets/tcpClient/Test/test_tcp_client.cpp | 40 - VrNets/tcpServer/CMakeLists.txt | 8 + VrNets/tcpServer/Inc/IVrTcpServer.h | 38 - VrNets/tcpServer/Inc/IYTCPServer.h | 56 + VrNets/tcpServer/Inc/VrTcpServer.h | 48 - VrNets/tcpServer/Src/CYServerTask.cpp | 263 ++++ VrNets/tcpServer/Src/CYTCPServer.cpp | 420 +++++ VrNets/tcpServer/Src/VrTcpServer.cpp | 281 ---- VrNets/tcpServer/Test/test_tcp_server.cpp | 60 - VrNets/tcpServer/_Inc/CYServerTask.h | 115 ++ VrNets/tcpServer/_Inc/CYTCPServer.h | 92 ++ 236 files changed, 11843 insertions(+), 827 deletions(-) create mode 100644 App/BeltTearing/BeltTearing.pro rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/BeltTearingApp.pro (80%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/IStatusUpdate.h (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/Presenter/Inc/BeltTearingPresenter.h (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/Presenter/Inc/PathManager.h (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/Presenter/Src/BeltTearingPresenter.cpp (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/Presenter/Src/PathManager.cpp (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/dialognetconfig.cpp (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/dialognetconfig.h (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/dialognetconfig.ui (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/main.cpp (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/mainwindow.cpp (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/mainwindow.h (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/mainwindow.ui (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/models/ImageInfoModel.cpp (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/models/ImageInfoModel.h (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/camera_offline.png (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/camera_online.png (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/close.png (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/config_algo.png (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/config_algo_s.png (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/config_camera.png (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/config_camera_level.png (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/config_camera_level_s.png (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/config_camera_s.png (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/config_data_test.png (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/config_data_test.svg (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/config_data_test_s.png (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/config_model.png (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/dialog_cancel.png (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/dialog_ok.png (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/hide.png (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/logo.ico (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/logo.png (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/result_icon.png (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/robot_offline.png (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/robot_online.png (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/start.png (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resource/stop.png (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/resources.qrc (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/widgets/ClickableFrame.h (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/widgets/DeviceStatusWidget.cpp (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/widgets/DeviceStatusWidget.h (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/widgets/ImageGridWidget.cpp (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/widgets/ImageGridWidget.h (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/widgets/ImageGridWithTableWidget.cpp (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/widgets/ImageGridWithTableWidget.h (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/widgets/ImageTileWidget.cpp (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/widgets/ImageTileWidget.h (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/widgets/StyledMessageBox.cpp (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/widgets/StyledMessageBox.h (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/widgets/TearingDataTableWidget.cpp (100%) rename {BeltTearingApp => App/BeltTearing/BeltTearingApp}/widgets/TearingDataTableWidget.h (100%) rename {BeltTearingConfig => App/BeltTearing/BeltTearingConfig}/BeltTearingConfig.pro (75%) rename {BeltTearingConfig => App/BeltTearing/BeltTearingConfig}/Inc/IVrBeltTearingConfig.h (100%) rename {BeltTearingConfig => App/BeltTearing/BeltTearingConfig}/Inc/VrBeltTearingConfig.h (100%) rename {BeltTearingConfig => App/BeltTearing/BeltTearingConfig}/Src/VrBeltTearingConfig.cpp (100%) rename {BeltTearingConfig => App/BeltTearing/BeltTearingConfig}/config/config.xml (100%) rename {BeltTearingServer => App/BeltTearing/BeltTearingServer}/BeltTearingAlgo.cpp (100%) rename {BeltTearingServer => App/BeltTearing/BeltTearingServer}/BeltTearingAlgo.h (100%) rename {BeltTearingServer => App/BeltTearing/BeltTearingServer}/BeltTearingPresenter.cpp (100%) rename {BeltTearingServer => App/BeltTearing/BeltTearingServer}/BeltTearingPresenter.h (100%) rename {BeltTearingServer => App/BeltTearing/BeltTearingServer}/BeltTearingServer.pro (62%) rename {BeltTearingServer => App/BeltTearing/BeltTearingServer}/PathManager.cpp (100%) rename {BeltTearingServer => App/BeltTearing/BeltTearingServer}/PathManager.h (100%) rename {BeltTearingServer => App/BeltTearing/BeltTearingServer}/SG_baseAlgo_Export.h (100%) rename {BeltTearingServer => App/BeltTearing/BeltTearingServer}/SG_baseDataType.h (100%) rename {BeltTearingServer => App/BeltTearing/BeltTearingServer}/SG_errCode.h (100%) rename {BeltTearingServer => App/BeltTearing/BeltTearingServer}/Version.h (100%) rename {BeltTearingServer => App/BeltTearing/BeltTearingServer}/beltTearingDetection.cpp (100%) rename {BeltTearingServer => App/BeltTearing/BeltTearingServer}/beltTearingDetection_Export.h (100%) rename {BeltTearingServer => App/BeltTearing/BeltTearingServer}/main.cpp (100%) rename {Doc => App/GrabBag/Doc}/ModbusTCP_编织袋拆垛项目相机端通信协议.docx (100%) rename {Doc => App/GrabBag/Doc}/RS485_拆垛机协议.docx (100%) create mode 100644 App/GrabBag/Doc/拆垛机tcpip协议.docx create mode 100644 App/GrabBag/GrabBag.pro create mode 100644 App/GrabBag/GrabBagApp/GrabBagApp.pro rename {GrabBagApp => App/GrabBag/GrabBagApp}/IYGrabBagStatus.h (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/Presenter/Inc/ConfigManager.h (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/Presenter/Inc/DetectPresenter.h (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/Presenter/Inc/GrabBagPresenter.h (88%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/Presenter/Inc/ProtocolCommon.h (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/Presenter/Inc/RobotProtocol.h (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/Presenter/Inc/SerialProtocol.h (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/Presenter/Src/ConfigManager.cpp (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/Presenter/Src/DetectPresenter.cpp (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/Presenter/Src/GrabBagPresenter.cpp (91%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/Presenter/Src/RobotProtocol.cpp (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/Presenter/Src/SerialProtocol.cpp (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/Utils/Inc/LaserDataLoader.h (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/Utils/Inc/PathManager.h (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/Utils/Inc/PointCloudImageUtils.h (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/Utils/Src/LaserDataLoader.cpp (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/Utils/Src/PathManager.cpp (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/Utils/Src/PointCloudImageUtils.cpp (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/Version.h (70%) rename {GrabBagPrj/Version => App/GrabBag/GrabBagApp}/Version.md (90%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/devstatus.cpp (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/devstatus.h (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/devstatus.ui (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/dialogcamera.cpp (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/dialogcamera.h (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/dialogcamera.ui (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/dialogcameralevel.cpp (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/dialogcameralevel.h (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/dialogcameralevel.ui (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/dialogconfig.cpp (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/dialogconfig.h (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/dialogconfig.ui (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/main.cpp (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/mainwindow.cpp (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/mainwindow.h (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/mainwindow.ui (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/camera_offline.png (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/camera_online.png (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/close.png (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/config_algo.png (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/config_algo_s.png (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/config_camera.png (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/config_camera_level.png (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/config_camera_level_s.png (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/config_camera_s.png (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/config_data_test.png (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/config_data_test.svg (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/config_data_test_s.png (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/config_model.png (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/dialog_cancel.png (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/dialog_ok.png (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/hide.png (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/logo.ico (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/logo.png (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/result_icon.png (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/robot_offline.png (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/robot_online.png (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/start.png (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resource/stop.png (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resources.qrc (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resultitem.cpp (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resultitem.h (100%) rename {GrabBagApp => App/GrabBag/GrabBagApp}/resultitem.ui (100%) rename {GrabBagConfig => App/GrabBag/GrabBagConfig}/CMakeLists.txt (100%) rename {GrabBagConfig => App/GrabBag/GrabBagConfig}/GrabBagConfig.pro (68%) rename {GrabBagConfig => App/GrabBag/GrabBagConfig}/Inc/IVrConfig.h (93%) rename {GrabBagConfig => App/GrabBag/GrabBagConfig}/Src/VrConfig.cpp (95%) rename {GrabBagConfig => App/GrabBag/GrabBagConfig}/_Inc/VrConfig.h (100%) rename {GrabBagConfig => App/GrabBag/GrabBagConfig}/config/EyeHandCalibMatrixInfo.ini (100%) rename {GrabBagConfig => App/GrabBag/GrabBagConfig}/config/config.xml (96%) rename {GrabBagConfigCmd => App/GrabBag/GrabBagConfigCmd}/ConfigCmdParser.cpp (100%) rename {GrabBagConfigCmd => App/GrabBag/GrabBagConfigCmd}/ConfigCmdParser.h (100%) rename {GrabBagConfigCmd => App/GrabBag/GrabBagConfigCmd}/GrabBagConfigCmd.pro (100%) rename {GrabBagConfigCmd => App/GrabBag/GrabBagConfigCmd}/main.cpp (100%) rename {GrabBagConfigCmd => App/GrabBag/GrabBagConfigCmd}/使用示例.md (100%) create mode 100644 App/LapWeld/LapWeld.pro create mode 100644 App/LapWeld/LapWeldApp/IYLapWeldStatus.h rename GrabBagApp/GrabBagApp.pro => App/LapWeld/LapWeldApp/LapWeldApp.pro (53%) create mode 100644 App/LapWeld/LapWeldApp/Presenter/Inc/ConfigManager.h create mode 100644 App/LapWeld/LapWeldApp/Presenter/Inc/DetectPresenter.h create mode 100644 App/LapWeld/LapWeldApp/Presenter/Inc/LapWeldPresenter.h create mode 100644 App/LapWeld/LapWeldApp/Presenter/Inc/ProtocolCommon.h create mode 100644 App/LapWeld/LapWeldApp/Presenter/Inc/RobotProtocol.h create mode 100644 App/LapWeld/LapWeldApp/Presenter/Inc/SerialProtocol.h create mode 100644 App/LapWeld/LapWeldApp/Presenter/Src/ConfigManager.cpp create mode 100644 App/LapWeld/LapWeldApp/Presenter/Src/DetectPresenter.cpp create mode 100644 App/LapWeld/LapWeldApp/Presenter/Src/LapWeldPresenter.cpp create mode 100644 App/LapWeld/LapWeldApp/Presenter/Src/RobotProtocol.cpp create mode 100644 App/LapWeld/LapWeldApp/Presenter/Src/SerialProtocol.cpp create mode 100644 App/LapWeld/LapWeldApp/Utils/Inc/LaserDataLoader.h create mode 100644 App/LapWeld/LapWeldApp/Utils/Inc/PathManager.h create mode 100644 App/LapWeld/LapWeldApp/Utils/Inc/PointCloudImageUtils.h create mode 100644 App/LapWeld/LapWeldApp/Utils/Src/LaserDataLoader.cpp create mode 100644 App/LapWeld/LapWeldApp/Utils/Src/PathManager.cpp create mode 100644 App/LapWeld/LapWeldApp/Utils/Src/PointCloudImageUtils.cpp create mode 100644 App/LapWeld/LapWeldApp/Version.h create mode 100644 App/LapWeld/LapWeldApp/devstatus.cpp create mode 100644 App/LapWeld/LapWeldApp/devstatus.h create mode 100644 App/LapWeld/LapWeldApp/devstatus.ui create mode 100644 App/LapWeld/LapWeldApp/dialogcamera.cpp create mode 100644 App/LapWeld/LapWeldApp/dialogcamera.h create mode 100644 App/LapWeld/LapWeldApp/dialogcamera.ui create mode 100644 App/LapWeld/LapWeldApp/dialogcameralevel.cpp create mode 100644 App/LapWeld/LapWeldApp/dialogcameralevel.h create mode 100644 App/LapWeld/LapWeldApp/dialogcameralevel.ui create mode 100644 App/LapWeld/LapWeldApp/main.cpp create mode 100644 App/LapWeld/LapWeldApp/mainwindow.cpp create mode 100644 App/LapWeld/LapWeldApp/mainwindow.h create mode 100644 App/LapWeld/LapWeldApp/mainwindow.ui create mode 100644 App/LapWeld/LapWeldApp/resource/camera_offline.png create mode 100644 App/LapWeld/LapWeldApp/resource/camera_online.png create mode 100644 App/LapWeld/LapWeldApp/resource/close.png create mode 100644 App/LapWeld/LapWeldApp/resource/config_algo.png create mode 100644 App/LapWeld/LapWeldApp/resource/config_algo_s.png create mode 100644 App/LapWeld/LapWeldApp/resource/config_camera.png create mode 100644 App/LapWeld/LapWeldApp/resource/config_camera_level.png create mode 100644 App/LapWeld/LapWeldApp/resource/config_camera_level_s.png create mode 100644 App/LapWeld/LapWeldApp/resource/config_camera_s.png create mode 100644 App/LapWeld/LapWeldApp/resource/config_data_test.png create mode 100644 App/LapWeld/LapWeldApp/resource/config_data_test.svg create mode 100644 App/LapWeld/LapWeldApp/resource/config_data_test_s.png create mode 100644 App/LapWeld/LapWeldApp/resource/config_model.png create mode 100644 App/LapWeld/LapWeldApp/resource/dialog_cancel.png create mode 100644 App/LapWeld/LapWeldApp/resource/dialog_ok.png create mode 100644 App/LapWeld/LapWeldApp/resource/hide.png create mode 100644 App/LapWeld/LapWeldApp/resource/logo.ico create mode 100644 App/LapWeld/LapWeldApp/resource/logo.png create mode 100644 App/LapWeld/LapWeldApp/resource/result_icon.png create mode 100644 App/LapWeld/LapWeldApp/resource/robot_offline.png create mode 100644 App/LapWeld/LapWeldApp/resource/robot_online.png create mode 100644 App/LapWeld/LapWeldApp/resource/start.png create mode 100644 App/LapWeld/LapWeldApp/resource/stop.png create mode 100644 App/LapWeld/LapWeldApp/resources.qrc create mode 100644 App/LapWeld/LapWeldApp/resultitem.cpp create mode 100644 App/LapWeld/LapWeldApp/resultitem.h create mode 100644 App/LapWeld/LapWeldApp/resultitem.ui create mode 100644 App/LapWeld/LapWeldConfig/Inc/IVrConfig.h create mode 100644 App/LapWeld/LapWeldConfig/LapWeldConfig.pro create mode 100644 App/LapWeld/LapWeldConfig/Src/VrConfig.cpp create mode 100644 App/LapWeld/LapWeldConfig/_Inc/VrConfig.h create mode 100644 App/LapWeld/LapWeldConfig/config/EyeHandCalibMatrixInfo.ini create mode 100644 App/LapWeld/LapWeldConfig/config/config.xml delete mode 100644 VrNets/tcpClient/Inc/IVrTcpClient.h delete mode 100644 VrNets/tcpClient/Inc/VrTcpClient.h delete mode 100644 VrNets/tcpClient/Src/VrTcpClient.cpp delete mode 100644 VrNets/tcpClient/Test/test_tcp_client.cpp create mode 100644 VrNets/tcpServer/CMakeLists.txt delete mode 100644 VrNets/tcpServer/Inc/IVrTcpServer.h create mode 100644 VrNets/tcpServer/Inc/IYTCPServer.h delete mode 100644 VrNets/tcpServer/Inc/VrTcpServer.h create mode 100644 VrNets/tcpServer/Src/CYServerTask.cpp create mode 100644 VrNets/tcpServer/Src/CYTCPServer.cpp delete mode 100644 VrNets/tcpServer/Src/VrTcpServer.cpp delete mode 100644 VrNets/tcpServer/Test/test_tcp_server.cpp create mode 100644 VrNets/tcpServer/_Inc/CYServerTask.h create mode 100644 VrNets/tcpServer/_Inc/CYTCPServer.h diff --git a/App/BeltTearing/BeltTearing.pro b/App/BeltTearing/BeltTearing.pro new file mode 100644 index 0000000..4a86a26 --- /dev/null +++ b/App/BeltTearing/BeltTearing.pro @@ -0,0 +1,12 @@ +TEMPLATE = subdirs + +# 撕裂项目 +SUBDIRS += BeltTearingConfig/BeltTearingConfig.pro +SUBDIRS += BeltTearingApp/BeltTearingApp.pro +SUBDIRS += BeltTearingServer/BeltTearingServer.pro + +# 设置编译顺序依赖 +BeltTearingApp.depends += ../../../VrNets/VrTcpClient.pro +BeltTearingApp.depends += BeltTearingConfig/BeltTearingConfig.pro + +BeltTearingServer.depends += BeltTearingConfig/BeltTearingConfig.pro diff --git a/BeltTearingApp/BeltTearingApp.pro b/App/BeltTearing/BeltTearingApp/BeltTearingApp.pro similarity index 80% rename from BeltTearingApp/BeltTearingApp.pro rename to App/BeltTearing/BeltTearingApp/BeltTearingApp.pro index c42b08e..d100f8c 100644 --- a/BeltTearingApp/BeltTearingApp.pro +++ b/App/BeltTearing/BeltTearingApp/BeltTearingApp.pro @@ -13,22 +13,24 @@ win32-msvc { # Include paths INCLUDEPATH += $$PWD/Presenter/Inc INCLUDEPATH += ../BeltTearingConfig/Inc -INCLUDEPATH += ../VrNets/tcpClient/Inc -INCLUDEPATH += ../VrUtils/Inc + + +INCLUDEPATH += ../../../VrNets/tcpClient/Inc +INCLUDEPATH += ../../../VrUtils/Inc # Link libraries win32:CONFIG(debug, debug|release) { LIBS += -L../BeltTearingConfig/debug -lBeltTearingConfig - LIBS += -L../VrNets/debug -lVrTcpClient - LIBS += -L../VrUtils/debug -lVrUtils + LIBS += -L../../../VrNets/debug -lVrTcpClient + LIBS += -L../../../VrUtils/debug -lVrUtils } else:win32:CONFIG(release, debug|release) { LIBS += -L../BeltTearingConfig/release -lBeltTearingConfig - LIBS += -L../VrNets/release -lVrTcpClient - LIBS += -L../VrUtils/release -lVrUtils + LIBS += -L../../../VrNets/release -lVrTcpClient + LIBS += -L../../../VrUtils/release -lVrUtils }else:unix:!macx { - LIBS += -L../BeltTearingConfig -lBeltTearingConfig - LIBS += -L../VrNets -lVrTcpClient - LIBS += -L../VrUtils -lVrUtils + LIBS += -L../BeltTearingConfig -lBeltTearingConfig + LIBS += -L../../../VrNets -lVrTcpClient + LIBS += -L../../../VrUtils -lVrUtils } # You can make your code fail to compile if it uses deprecated APIs. diff --git a/BeltTearingApp/IStatusUpdate.h b/App/BeltTearing/BeltTearingApp/IStatusUpdate.h similarity index 100% rename from BeltTearingApp/IStatusUpdate.h rename to App/BeltTearing/BeltTearingApp/IStatusUpdate.h diff --git a/BeltTearingApp/Presenter/Inc/BeltTearingPresenter.h b/App/BeltTearing/BeltTearingApp/Presenter/Inc/BeltTearingPresenter.h similarity index 100% rename from BeltTearingApp/Presenter/Inc/BeltTearingPresenter.h rename to App/BeltTearing/BeltTearingApp/Presenter/Inc/BeltTearingPresenter.h diff --git a/BeltTearingApp/Presenter/Inc/PathManager.h b/App/BeltTearing/BeltTearingApp/Presenter/Inc/PathManager.h similarity index 100% rename from BeltTearingApp/Presenter/Inc/PathManager.h rename to App/BeltTearing/BeltTearingApp/Presenter/Inc/PathManager.h diff --git a/BeltTearingApp/Presenter/Src/BeltTearingPresenter.cpp b/App/BeltTearing/BeltTearingApp/Presenter/Src/BeltTearingPresenter.cpp similarity index 100% rename from BeltTearingApp/Presenter/Src/BeltTearingPresenter.cpp rename to App/BeltTearing/BeltTearingApp/Presenter/Src/BeltTearingPresenter.cpp diff --git a/BeltTearingApp/Presenter/Src/PathManager.cpp b/App/BeltTearing/BeltTearingApp/Presenter/Src/PathManager.cpp similarity index 100% rename from BeltTearingApp/Presenter/Src/PathManager.cpp rename to App/BeltTearing/BeltTearingApp/Presenter/Src/PathManager.cpp diff --git a/BeltTearingApp/dialognetconfig.cpp b/App/BeltTearing/BeltTearingApp/dialognetconfig.cpp similarity index 100% rename from BeltTearingApp/dialognetconfig.cpp rename to App/BeltTearing/BeltTearingApp/dialognetconfig.cpp diff --git a/BeltTearingApp/dialognetconfig.h b/App/BeltTearing/BeltTearingApp/dialognetconfig.h similarity index 100% rename from BeltTearingApp/dialognetconfig.h rename to App/BeltTearing/BeltTearingApp/dialognetconfig.h diff --git a/BeltTearingApp/dialognetconfig.ui b/App/BeltTearing/BeltTearingApp/dialognetconfig.ui similarity index 100% rename from BeltTearingApp/dialognetconfig.ui rename to App/BeltTearing/BeltTearingApp/dialognetconfig.ui diff --git a/BeltTearingApp/main.cpp b/App/BeltTearing/BeltTearingApp/main.cpp similarity index 100% rename from BeltTearingApp/main.cpp rename to App/BeltTearing/BeltTearingApp/main.cpp diff --git a/BeltTearingApp/mainwindow.cpp b/App/BeltTearing/BeltTearingApp/mainwindow.cpp similarity index 100% rename from BeltTearingApp/mainwindow.cpp rename to App/BeltTearing/BeltTearingApp/mainwindow.cpp diff --git a/BeltTearingApp/mainwindow.h b/App/BeltTearing/BeltTearingApp/mainwindow.h similarity index 100% rename from BeltTearingApp/mainwindow.h rename to App/BeltTearing/BeltTearingApp/mainwindow.h diff --git a/BeltTearingApp/mainwindow.ui b/App/BeltTearing/BeltTearingApp/mainwindow.ui similarity index 100% rename from BeltTearingApp/mainwindow.ui rename to App/BeltTearing/BeltTearingApp/mainwindow.ui diff --git a/BeltTearingApp/models/ImageInfoModel.cpp b/App/BeltTearing/BeltTearingApp/models/ImageInfoModel.cpp similarity index 100% rename from BeltTearingApp/models/ImageInfoModel.cpp rename to App/BeltTearing/BeltTearingApp/models/ImageInfoModel.cpp diff --git a/BeltTearingApp/models/ImageInfoModel.h b/App/BeltTearing/BeltTearingApp/models/ImageInfoModel.h similarity index 100% rename from BeltTearingApp/models/ImageInfoModel.h rename to App/BeltTearing/BeltTearingApp/models/ImageInfoModel.h diff --git a/BeltTearingApp/resource/camera_offline.png b/App/BeltTearing/BeltTearingApp/resource/camera_offline.png similarity index 100% rename from BeltTearingApp/resource/camera_offline.png rename to App/BeltTearing/BeltTearingApp/resource/camera_offline.png diff --git a/BeltTearingApp/resource/camera_online.png b/App/BeltTearing/BeltTearingApp/resource/camera_online.png similarity index 100% rename from BeltTearingApp/resource/camera_online.png rename to App/BeltTearing/BeltTearingApp/resource/camera_online.png diff --git a/BeltTearingApp/resource/close.png b/App/BeltTearing/BeltTearingApp/resource/close.png similarity index 100% rename from BeltTearingApp/resource/close.png rename to App/BeltTearing/BeltTearingApp/resource/close.png diff --git a/BeltTearingApp/resource/config_algo.png b/App/BeltTearing/BeltTearingApp/resource/config_algo.png similarity index 100% rename from BeltTearingApp/resource/config_algo.png rename to App/BeltTearing/BeltTearingApp/resource/config_algo.png diff --git a/BeltTearingApp/resource/config_algo_s.png b/App/BeltTearing/BeltTearingApp/resource/config_algo_s.png similarity index 100% rename from BeltTearingApp/resource/config_algo_s.png rename to App/BeltTearing/BeltTearingApp/resource/config_algo_s.png diff --git a/BeltTearingApp/resource/config_camera.png b/App/BeltTearing/BeltTearingApp/resource/config_camera.png similarity index 100% rename from BeltTearingApp/resource/config_camera.png rename to App/BeltTearing/BeltTearingApp/resource/config_camera.png diff --git a/BeltTearingApp/resource/config_camera_level.png b/App/BeltTearing/BeltTearingApp/resource/config_camera_level.png similarity index 100% rename from BeltTearingApp/resource/config_camera_level.png rename to App/BeltTearing/BeltTearingApp/resource/config_camera_level.png diff --git a/BeltTearingApp/resource/config_camera_level_s.png b/App/BeltTearing/BeltTearingApp/resource/config_camera_level_s.png similarity index 100% rename from BeltTearingApp/resource/config_camera_level_s.png rename to App/BeltTearing/BeltTearingApp/resource/config_camera_level_s.png diff --git a/BeltTearingApp/resource/config_camera_s.png b/App/BeltTearing/BeltTearingApp/resource/config_camera_s.png similarity index 100% rename from BeltTearingApp/resource/config_camera_s.png rename to App/BeltTearing/BeltTearingApp/resource/config_camera_s.png diff --git a/BeltTearingApp/resource/config_data_test.png b/App/BeltTearing/BeltTearingApp/resource/config_data_test.png similarity index 100% rename from BeltTearingApp/resource/config_data_test.png rename to App/BeltTearing/BeltTearingApp/resource/config_data_test.png diff --git a/BeltTearingApp/resource/config_data_test.svg b/App/BeltTearing/BeltTearingApp/resource/config_data_test.svg similarity index 100% rename from BeltTearingApp/resource/config_data_test.svg rename to App/BeltTearing/BeltTearingApp/resource/config_data_test.svg diff --git a/BeltTearingApp/resource/config_data_test_s.png b/App/BeltTearing/BeltTearingApp/resource/config_data_test_s.png similarity index 100% rename from BeltTearingApp/resource/config_data_test_s.png rename to App/BeltTearing/BeltTearingApp/resource/config_data_test_s.png diff --git a/BeltTearingApp/resource/config_model.png b/App/BeltTearing/BeltTearingApp/resource/config_model.png similarity index 100% rename from BeltTearingApp/resource/config_model.png rename to App/BeltTearing/BeltTearingApp/resource/config_model.png diff --git a/BeltTearingApp/resource/dialog_cancel.png b/App/BeltTearing/BeltTearingApp/resource/dialog_cancel.png similarity index 100% rename from BeltTearingApp/resource/dialog_cancel.png rename to App/BeltTearing/BeltTearingApp/resource/dialog_cancel.png diff --git a/BeltTearingApp/resource/dialog_ok.png b/App/BeltTearing/BeltTearingApp/resource/dialog_ok.png similarity index 100% rename from BeltTearingApp/resource/dialog_ok.png rename to App/BeltTearing/BeltTearingApp/resource/dialog_ok.png diff --git a/BeltTearingApp/resource/hide.png b/App/BeltTearing/BeltTearingApp/resource/hide.png similarity index 100% rename from BeltTearingApp/resource/hide.png rename to App/BeltTearing/BeltTearingApp/resource/hide.png diff --git a/BeltTearingApp/resource/logo.ico b/App/BeltTearing/BeltTearingApp/resource/logo.ico similarity index 100% rename from BeltTearingApp/resource/logo.ico rename to App/BeltTearing/BeltTearingApp/resource/logo.ico diff --git a/BeltTearingApp/resource/logo.png b/App/BeltTearing/BeltTearingApp/resource/logo.png similarity index 100% rename from BeltTearingApp/resource/logo.png rename to App/BeltTearing/BeltTearingApp/resource/logo.png diff --git a/BeltTearingApp/resource/result_icon.png b/App/BeltTearing/BeltTearingApp/resource/result_icon.png similarity index 100% rename from BeltTearingApp/resource/result_icon.png rename to App/BeltTearing/BeltTearingApp/resource/result_icon.png diff --git a/BeltTearingApp/resource/robot_offline.png b/App/BeltTearing/BeltTearingApp/resource/robot_offline.png similarity index 100% rename from BeltTearingApp/resource/robot_offline.png rename to App/BeltTearing/BeltTearingApp/resource/robot_offline.png diff --git a/BeltTearingApp/resource/robot_online.png b/App/BeltTearing/BeltTearingApp/resource/robot_online.png similarity index 100% rename from BeltTearingApp/resource/robot_online.png rename to App/BeltTearing/BeltTearingApp/resource/robot_online.png diff --git a/BeltTearingApp/resource/start.png b/App/BeltTearing/BeltTearingApp/resource/start.png similarity index 100% rename from BeltTearingApp/resource/start.png rename to App/BeltTearing/BeltTearingApp/resource/start.png diff --git a/BeltTearingApp/resource/stop.png b/App/BeltTearing/BeltTearingApp/resource/stop.png similarity index 100% rename from BeltTearingApp/resource/stop.png rename to App/BeltTearing/BeltTearingApp/resource/stop.png diff --git a/BeltTearingApp/resources.qrc b/App/BeltTearing/BeltTearingApp/resources.qrc similarity index 100% rename from BeltTearingApp/resources.qrc rename to App/BeltTearing/BeltTearingApp/resources.qrc diff --git a/BeltTearingApp/widgets/ClickableFrame.h b/App/BeltTearing/BeltTearingApp/widgets/ClickableFrame.h similarity index 100% rename from BeltTearingApp/widgets/ClickableFrame.h rename to App/BeltTearing/BeltTearingApp/widgets/ClickableFrame.h diff --git a/BeltTearingApp/widgets/DeviceStatusWidget.cpp b/App/BeltTearing/BeltTearingApp/widgets/DeviceStatusWidget.cpp similarity index 100% rename from BeltTearingApp/widgets/DeviceStatusWidget.cpp rename to App/BeltTearing/BeltTearingApp/widgets/DeviceStatusWidget.cpp diff --git a/BeltTearingApp/widgets/DeviceStatusWidget.h b/App/BeltTearing/BeltTearingApp/widgets/DeviceStatusWidget.h similarity index 100% rename from BeltTearingApp/widgets/DeviceStatusWidget.h rename to App/BeltTearing/BeltTearingApp/widgets/DeviceStatusWidget.h diff --git a/BeltTearingApp/widgets/ImageGridWidget.cpp b/App/BeltTearing/BeltTearingApp/widgets/ImageGridWidget.cpp similarity index 100% rename from BeltTearingApp/widgets/ImageGridWidget.cpp rename to App/BeltTearing/BeltTearingApp/widgets/ImageGridWidget.cpp diff --git a/BeltTearingApp/widgets/ImageGridWidget.h b/App/BeltTearing/BeltTearingApp/widgets/ImageGridWidget.h similarity index 100% rename from BeltTearingApp/widgets/ImageGridWidget.h rename to App/BeltTearing/BeltTearingApp/widgets/ImageGridWidget.h diff --git a/BeltTearingApp/widgets/ImageGridWithTableWidget.cpp b/App/BeltTearing/BeltTearingApp/widgets/ImageGridWithTableWidget.cpp similarity index 100% rename from BeltTearingApp/widgets/ImageGridWithTableWidget.cpp rename to App/BeltTearing/BeltTearingApp/widgets/ImageGridWithTableWidget.cpp diff --git a/BeltTearingApp/widgets/ImageGridWithTableWidget.h b/App/BeltTearing/BeltTearingApp/widgets/ImageGridWithTableWidget.h similarity index 100% rename from BeltTearingApp/widgets/ImageGridWithTableWidget.h rename to App/BeltTearing/BeltTearingApp/widgets/ImageGridWithTableWidget.h diff --git a/BeltTearingApp/widgets/ImageTileWidget.cpp b/App/BeltTearing/BeltTearingApp/widgets/ImageTileWidget.cpp similarity index 100% rename from BeltTearingApp/widgets/ImageTileWidget.cpp rename to App/BeltTearing/BeltTearingApp/widgets/ImageTileWidget.cpp diff --git a/BeltTearingApp/widgets/ImageTileWidget.h b/App/BeltTearing/BeltTearingApp/widgets/ImageTileWidget.h similarity index 100% rename from BeltTearingApp/widgets/ImageTileWidget.h rename to App/BeltTearing/BeltTearingApp/widgets/ImageTileWidget.h diff --git a/BeltTearingApp/widgets/StyledMessageBox.cpp b/App/BeltTearing/BeltTearingApp/widgets/StyledMessageBox.cpp similarity index 100% rename from BeltTearingApp/widgets/StyledMessageBox.cpp rename to App/BeltTearing/BeltTearingApp/widgets/StyledMessageBox.cpp diff --git a/BeltTearingApp/widgets/StyledMessageBox.h b/App/BeltTearing/BeltTearingApp/widgets/StyledMessageBox.h similarity index 100% rename from BeltTearingApp/widgets/StyledMessageBox.h rename to App/BeltTearing/BeltTearingApp/widgets/StyledMessageBox.h diff --git a/BeltTearingApp/widgets/TearingDataTableWidget.cpp b/App/BeltTearing/BeltTearingApp/widgets/TearingDataTableWidget.cpp similarity index 100% rename from BeltTearingApp/widgets/TearingDataTableWidget.cpp rename to App/BeltTearing/BeltTearingApp/widgets/TearingDataTableWidget.cpp diff --git a/BeltTearingApp/widgets/TearingDataTableWidget.h b/App/BeltTearing/BeltTearingApp/widgets/TearingDataTableWidget.h similarity index 100% rename from BeltTearingApp/widgets/TearingDataTableWidget.h rename to App/BeltTearing/BeltTearingApp/widgets/TearingDataTableWidget.h diff --git a/BeltTearingConfig/BeltTearingConfig.pro b/App/BeltTearing/BeltTearingConfig/BeltTearingConfig.pro similarity index 75% rename from BeltTearingConfig/BeltTearingConfig.pro rename to App/BeltTearing/BeltTearingConfig/BeltTearingConfig.pro index c2e91d3..0441e88 100644 --- a/BeltTearingConfig/BeltTearingConfig.pro +++ b/App/BeltTearing/BeltTearingConfig/BeltTearingConfig.pro @@ -26,13 +26,13 @@ SOURCES += \ $$PWD/Src/VrBeltTearingConfig.cpp #utils -INCLUDEPATH += $$PWD/../VrUtils/Inc -INCLUDEPATH += $$PWD/../VrUtils/tinyxml2 +INCLUDEPATH += $$PWD/../../../VrUtils/Inc +INCLUDEPATH += $$PWD/../../../VrUtils/tinyxml2 win32:CONFIG(debug, debug|release) { - LIBS += -L../VrUtils/Debug -lVrUtils + LIBS += -L../../../VrUtils/Debug -lVrUtils }else:win32:CONFIG(release, debug|release){ - LIBS += -L../VrUtils/release -lVrUtils + LIBS += -L../../../VrUtils/release -lVrUtils }else:unix:!macx { # Unix/Linux平台库链接(包括交叉编译) LIBS += -L../VrUtils -lVrUtils diff --git a/BeltTearingConfig/Inc/IVrBeltTearingConfig.h b/App/BeltTearing/BeltTearingConfig/Inc/IVrBeltTearingConfig.h similarity index 100% rename from BeltTearingConfig/Inc/IVrBeltTearingConfig.h rename to App/BeltTearing/BeltTearingConfig/Inc/IVrBeltTearingConfig.h diff --git a/BeltTearingConfig/Inc/VrBeltTearingConfig.h b/App/BeltTearing/BeltTearingConfig/Inc/VrBeltTearingConfig.h similarity index 100% rename from BeltTearingConfig/Inc/VrBeltTearingConfig.h rename to App/BeltTearing/BeltTearingConfig/Inc/VrBeltTearingConfig.h diff --git a/BeltTearingConfig/Src/VrBeltTearingConfig.cpp b/App/BeltTearing/BeltTearingConfig/Src/VrBeltTearingConfig.cpp similarity index 100% rename from BeltTearingConfig/Src/VrBeltTearingConfig.cpp rename to App/BeltTearing/BeltTearingConfig/Src/VrBeltTearingConfig.cpp diff --git a/BeltTearingConfig/config/config.xml b/App/BeltTearing/BeltTearingConfig/config/config.xml similarity index 100% rename from BeltTearingConfig/config/config.xml rename to App/BeltTearing/BeltTearingConfig/config/config.xml diff --git a/BeltTearingServer/BeltTearingAlgo.cpp b/App/BeltTearing/BeltTearingServer/BeltTearingAlgo.cpp similarity index 100% rename from BeltTearingServer/BeltTearingAlgo.cpp rename to App/BeltTearing/BeltTearingServer/BeltTearingAlgo.cpp diff --git a/BeltTearingServer/BeltTearingAlgo.h b/App/BeltTearing/BeltTearingServer/BeltTearingAlgo.h similarity index 100% rename from BeltTearingServer/BeltTearingAlgo.h rename to App/BeltTearing/BeltTearingServer/BeltTearingAlgo.h diff --git a/BeltTearingServer/BeltTearingPresenter.cpp b/App/BeltTearing/BeltTearingServer/BeltTearingPresenter.cpp similarity index 100% rename from BeltTearingServer/BeltTearingPresenter.cpp rename to App/BeltTearing/BeltTearingServer/BeltTearingPresenter.cpp diff --git a/BeltTearingServer/BeltTearingPresenter.h b/App/BeltTearing/BeltTearingServer/BeltTearingPresenter.h similarity index 100% rename from BeltTearingServer/BeltTearingPresenter.h rename to App/BeltTearing/BeltTearingServer/BeltTearingPresenter.h diff --git a/BeltTearingServer/BeltTearingServer.pro b/App/BeltTearing/BeltTearingServer/BeltTearingServer.pro similarity index 62% rename from BeltTearingServer/BeltTearingServer.pro rename to App/BeltTearing/BeltTearingServer/BeltTearingServer.pro index 6071414..eca4d23 100644 --- a/BeltTearingServer/BeltTearingServer.pro +++ b/App/BeltTearing/BeltTearingServer/BeltTearingServer.pro @@ -15,13 +15,13 @@ win32-msvc { # In order to do so, uncomment the following line. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 -INCLUDEPATH += $$PWD/../VrCommon/Inc -INCLUDEPATH += $$PWD/../VrUtils/Inc +INCLUDEPATH += $$PWD/../../../VrCommon/Inc +INCLUDEPATH += $$PWD/../../../VrUtils/Inc INCLUDEPATH += $$PWD/Presenter/Inc INCLUDEPATH += $$PWD/Utils/Inc -INCLUDEPATH += $$PWD/../VrConfig/Inc -INCLUDEPATH += $$PWD/../VrEyeDevice/Inc +INCLUDEPATH += $$PWD/../../../VrConfig/Inc +INCLUDEPATH += $$PWD/../../../VrEyeDevice/Inc INCLUDEPATH += $$PWD/../BeltTearingConfig/Inc SOURCES += \ @@ -45,18 +45,18 @@ FORMS += win32:CONFIG(debug, debug|release) { LIBS += -L../BeltTearingConfig/debug -lBeltTearingConfig - LIBS += -L../VrEyeDevice/debug -lVrEyeDevice - LIBS += -L../VrUtils/debug -lVrUtils + LIBS += -L../../../VrEyeDevice/debug -lVrEyeDevice + LIBS += -L../../../VrUtils/debug -lVrUtils }else:win32:CONFIG(release, debug|release){ LIBS += -L../BeltTearingConfig/release -lBeltTearingConfig - LIBS += -L../VrEyeDevice/release -lVrEyeDevice - LIBS += -L../VrUtils/release -lVrUtils + LIBS += -L../../../VrEyeDevice/release -lVrEyeDevice + LIBS += -L../../../VrUtils/release -lVrUtils }else:unix:!macx { # Unix/Linux平台库链接(包括交叉编译) # 注意链接顺序:依赖关系从高到低 - LIBS += -L../VrUtils -lVrUtils + LIBS += -L../../../VrUtils -lVrUtils + LIBS += -L../../../VrEyeDevice -lVrEyeDevice LIBS += -L../BeltTearingConfig -lBeltTearingConfig - LIBS += -L../VrEyeDevice -lVrEyeDevice # 添加系统库依赖 LIBS += -lpthread @@ -64,33 +64,33 @@ win32:CONFIG(debug, debug|release) { #linux下的为unix ,windows下用的win32 -INCLUDEPATH += ../SDK/VzNLSDK/_Inc -INCLUDEPATH += ../SDK/VzNLSDK/Inc +INCLUDEPATH += ../../../SDK/VzNLSDK/_Inc +INCLUDEPATH += ../../../SDK/VzNLSDK/Inc win32:CONFIG(release, debug|release): { - LIBS += -L$$PWD/../SDK/VzNLSDK/Windows/x64/Release + LIBS += -L$$PWD/../../../SDK/VzNLSDK/Windows/x64/Release LIBS += -lVzKernel -lVzNLDetect -lVzNLGraphics } else:win32:CONFIG(debug, debug|release): { - LIBS += -L$$PWD/../SDK/VzNLSDK/Windows/x64/Debug + LIBS += -L$$PWD/../../../SDK/VzNLSDK/Windows/x64/Debug LIBS += -lVzKerneld -lVzNLDetectd -lVzNLGraphicsd } else:unix:!macx: { - LIBS += -L$$PWD/../SDK/VzNLSDK/Arm/aarch64 + LIBS += -L$$PWD/../../../SDK/VzNLSDK/Arm/aarch64 LIBS += -lVzEyeSecurityLoader-shared -lVzKernel -lVzNLDetect -lVzNLGraphics } # 算法 -INCLUDEPATH += ../SDK/OpenCV320/include +INCLUDEPATH += ../../../SDK/OpenCV320/include win32:CONFIG(release, debug|release): { - LIBS += -L$$PWD/../SDK/OpenCV320/Windows/vc14/Release -lopencv_world320 + LIBS += -L$$PWD/../../../SDK/OpenCV320/Windows/vc14/Release -lopencv_world320 } else:win32:CONFIG(debug, debug|release): { - LIBS += -L$$PWD/../SDK/OpenCV320/Windows/vc14/Release -lopencv_world320 + LIBS += -L$$PWD/../../../SDK/OpenCV320/Windows/vc14/Release -lopencv_world320 } else:unix:!macx: { - LIBS += -L$$PWD/../SDK/OpenCV320/Arm/aarch64 -lopencv_core -lopencv_imgproc -lopencv_highgui + LIBS += -L$$PWD/../../../SDK/OpenCV320/Arm/aarch64 -lopencv_core -lopencv_imgproc -lopencv_highgui } diff --git a/BeltTearingServer/PathManager.cpp b/App/BeltTearing/BeltTearingServer/PathManager.cpp similarity index 100% rename from BeltTearingServer/PathManager.cpp rename to App/BeltTearing/BeltTearingServer/PathManager.cpp diff --git a/BeltTearingServer/PathManager.h b/App/BeltTearing/BeltTearingServer/PathManager.h similarity index 100% rename from BeltTearingServer/PathManager.h rename to App/BeltTearing/BeltTearingServer/PathManager.h diff --git a/BeltTearingServer/SG_baseAlgo_Export.h b/App/BeltTearing/BeltTearingServer/SG_baseAlgo_Export.h similarity index 100% rename from BeltTearingServer/SG_baseAlgo_Export.h rename to App/BeltTearing/BeltTearingServer/SG_baseAlgo_Export.h diff --git a/BeltTearingServer/SG_baseDataType.h b/App/BeltTearing/BeltTearingServer/SG_baseDataType.h similarity index 100% rename from BeltTearingServer/SG_baseDataType.h rename to App/BeltTearing/BeltTearingServer/SG_baseDataType.h diff --git a/BeltTearingServer/SG_errCode.h b/App/BeltTearing/BeltTearingServer/SG_errCode.h similarity index 100% rename from BeltTearingServer/SG_errCode.h rename to App/BeltTearing/BeltTearingServer/SG_errCode.h diff --git a/BeltTearingServer/Version.h b/App/BeltTearing/BeltTearingServer/Version.h similarity index 100% rename from BeltTearingServer/Version.h rename to App/BeltTearing/BeltTearingServer/Version.h diff --git a/BeltTearingServer/beltTearingDetection.cpp b/App/BeltTearing/BeltTearingServer/beltTearingDetection.cpp similarity index 100% rename from BeltTearingServer/beltTearingDetection.cpp rename to App/BeltTearing/BeltTearingServer/beltTearingDetection.cpp diff --git a/BeltTearingServer/beltTearingDetection_Export.h b/App/BeltTearing/BeltTearingServer/beltTearingDetection_Export.h similarity index 100% rename from BeltTearingServer/beltTearingDetection_Export.h rename to App/BeltTearing/BeltTearingServer/beltTearingDetection_Export.h diff --git a/BeltTearingServer/main.cpp b/App/BeltTearing/BeltTearingServer/main.cpp similarity index 100% rename from BeltTearingServer/main.cpp rename to App/BeltTearing/BeltTearingServer/main.cpp diff --git a/Doc/ModbusTCP_编织袋拆垛项目相机端通信协议.docx b/App/GrabBag/Doc/ModbusTCP_编织袋拆垛项目相机端通信协议.docx similarity index 100% rename from Doc/ModbusTCP_编织袋拆垛项目相机端通信协议.docx rename to App/GrabBag/Doc/ModbusTCP_编织袋拆垛项目相机端通信协议.docx diff --git a/Doc/RS485_拆垛机协议.docx b/App/GrabBag/Doc/RS485_拆垛机协议.docx similarity index 100% rename from Doc/RS485_拆垛机协议.docx rename to App/GrabBag/Doc/RS485_拆垛机协议.docx diff --git a/App/GrabBag/Doc/拆垛机tcpip协议.docx b/App/GrabBag/Doc/拆垛机tcpip协议.docx new file mode 100644 index 0000000000000000000000000000000000000000..6605ce30853b4ad5e126be92eeee63bac05287fd GIT binary patch literal 13313 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 #include +#include +#include #include "IVrConfig.h" #include "IVrEyeDevice.h" @@ -15,15 +17,20 @@ #include "LaserDataLoader.h" #include "PathManager.h" #include "ConfigManager.h" +#include "IYTCPServer.h" #include #include #include +#include #include -class GrabBagPresenter : public IVrConfigChangeNotify, public IConfigChangeListener + +class GrabBagPresenter : public QObject, public IVrConfigChangeNotify, public IConfigChangeListener { + Q_OBJECT + public: - GrabBagPresenter(); + explicit GrabBagPresenter(QObject *parent = nullptr); ~GrabBagPresenter(); // 初始化 @@ -66,7 +73,7 @@ public: virtual void OnSystemConfigChanged(const SystemConfig& config) override; virtual void OnCameraParamChanged(int cameraIndex, const CameraUIParam& cameraParam) override; virtual void OnAlgorithmParamChanged(const VrAlgorithmParams& algorithmParams) override; - + // 配置管理 ConfigManager* GetConfigManager() { return m_pConfigManager.get(); } @@ -81,6 +88,7 @@ public: static void _StaticCameraNotify(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pInfoParam); static void _StaticDetectionCallback(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint, void* pParam); + private: // 相机协议相关方法 @@ -92,6 +100,15 @@ private: // 串口协议相关方法 int InitSerialProtocol(); + // TCP服务器相关方法 + int InitTcpServer(int nPort); + // 初始化TCP服务器 + bool startServer(quint16 port = 0); // port = 0 表示使用配置文件中的端口 + void stopServer(); + + // TCP服务器回调处理方法 + void onTcpDataReceivedFromCallback(const TCPClient* pClient, const char* pData, const unsigned int nLen); + // 算法初始化接口 int InitAlgorithmParams(); @@ -140,7 +157,11 @@ private: RobotProtocol* m_pRobotProtocol = nullptr; SerialProtocol* m_pSerialProtocol = nullptr; - + + // TCP服务器相关 + IYTCPServer* m_pTcpServer = nullptr; + quint16 m_port = 0; + // 连接状态标志 bool m_bCameraConnected = false; // 相机连接状态 bool m_bRobotConnected = false; // 机械臂连接状态 diff --git a/GrabBagApp/Presenter/Inc/ProtocolCommon.h b/App/GrabBag/GrabBagApp/Presenter/Inc/ProtocolCommon.h similarity index 100% rename from GrabBagApp/Presenter/Inc/ProtocolCommon.h rename to App/GrabBag/GrabBagApp/Presenter/Inc/ProtocolCommon.h diff --git a/GrabBagApp/Presenter/Inc/RobotProtocol.h b/App/GrabBag/GrabBagApp/Presenter/Inc/RobotProtocol.h similarity index 100% rename from GrabBagApp/Presenter/Inc/RobotProtocol.h rename to App/GrabBag/GrabBagApp/Presenter/Inc/RobotProtocol.h diff --git a/GrabBagApp/Presenter/Inc/SerialProtocol.h b/App/GrabBag/GrabBagApp/Presenter/Inc/SerialProtocol.h similarity index 100% rename from GrabBagApp/Presenter/Inc/SerialProtocol.h rename to App/GrabBag/GrabBagApp/Presenter/Inc/SerialProtocol.h diff --git a/GrabBagApp/Presenter/Src/ConfigManager.cpp b/App/GrabBag/GrabBagApp/Presenter/Src/ConfigManager.cpp similarity index 100% rename from GrabBagApp/Presenter/Src/ConfigManager.cpp rename to App/GrabBag/GrabBagApp/Presenter/Src/ConfigManager.cpp diff --git a/GrabBagApp/Presenter/Src/DetectPresenter.cpp b/App/GrabBag/GrabBagApp/Presenter/Src/DetectPresenter.cpp similarity index 100% rename from GrabBagApp/Presenter/Src/DetectPresenter.cpp rename to App/GrabBag/GrabBagApp/Presenter/Src/DetectPresenter.cpp diff --git a/GrabBagApp/Presenter/Src/GrabBagPresenter.cpp b/App/GrabBag/GrabBagApp/Presenter/Src/GrabBagPresenter.cpp similarity index 91% rename from GrabBagApp/Presenter/Src/GrabBagPresenter.cpp rename to App/GrabBag/GrabBagApp/Presenter/Src/GrabBagPresenter.cpp index b6f346d..e9b709e 100644 --- a/GrabBagApp/Presenter/Src/GrabBagPresenter.cpp +++ b/App/GrabBag/GrabBagApp/Presenter/Src/GrabBagPresenter.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "Version.h" #include "VrTimeUtils.h" @@ -20,22 +21,26 @@ #include "VrConvert.h" #include "PointCloudImageUtils.h" -GrabBagPresenter::GrabBagPresenter() - : m_vrConfig(nullptr) +GrabBagPresenter::GrabBagPresenter(QObject *parent) + : QObject(parent) + , m_vrConfig(nullptr) , m_pStatus(nullptr) , m_pDetectPresenter(nullptr) , m_pRobotProtocol(nullptr) + , m_pSerialProtocol(nullptr) + , m_pTcpServer(nullptr) + , m_port(0) , m_bCameraConnected(false) , m_bRobotConnected(false) , m_currentWorkStatus(WorkStatus::Error) -{ +{ m_detectionDataCache.clear(); } GrabBagPresenter::~GrabBagPresenter() { // 停止配置管理器 - if (m_pConfigManager) { + if (m_pConfigManager) { m_pConfigManager->Shutdown(); m_pConfigManager.reset(); } @@ -66,6 +71,14 @@ GrabBagPresenter::~GrabBagPresenter() m_pSerialProtocol = nullptr; } + // 释放TCP服务器 + if (m_pTcpServer) { + m_pTcpServer->Stop(); + m_pTcpServer->Close(); + delete m_pTcpServer; + m_pTcpServer = nullptr; + } + // 释放相机设备资源 for(auto it = m_vrEyeDeviceList.begin(); it != m_vrEyeDeviceList.end(); it++) { @@ -153,6 +166,14 @@ int GrabBagPresenter::Init() m_pStatus->OnSerialConnectionChanged(true); } + // 初始化TCP服务器 + nRet = InitTcpServer(configResult.tcpServerConfig.port); + if (nRet != 0) { + m_pStatus->OnStatusUpdate("TCP服务器初始化失败"); + } else { + m_pStatus->OnStatusUpdate("TCP服务器初始化成功"); + } + m_bAlgoDetectThreadRunning = true; m_algoDetectThread = std::thread(&GrabBagPresenter::_AlgoDetectThread, this); m_algoDetectThread.detach(); @@ -237,8 +258,7 @@ int GrabBagPresenter::InitRobotProtocol() // 设置工作信号回调 m_pRobotProtocol->SetWorkSignalCallback([this](bool startWork, int cameraIndex) { return this->OnRobotWorkSignal(startWork, cameraIndex); - }); - + }); LOG_INFO("Robot protocol initialization completed successfully\n"); return nRet; @@ -298,6 +318,111 @@ int GrabBagPresenter::InitSerialProtocol() } } +// 初始化TCP服务器 +int GrabBagPresenter::InitTcpServer(int nPort) +{ + // 创建TCP服务器 + if (!VrCreatYTCPServer(&m_pTcpServer)) { + LOG_ERROR("Failed to create VrNets TCPServer\n"); + m_pTcpServer = nullptr; + } + + if (!m_pTcpServer) { + LOG_ERROR("TCP server is not initialized\n"); + return ERR_CODE(DEV_OPEN_ERR); + } + + LOG_INFO("Initializing VrNets TCP server on port %d\n", nPort); + + // 初始化TCP服务器 + if (!m_pTcpServer->Init(nPort, false)) { + LOG_ERROR("Failed to initialize VrNets TCP server on port %d\n", nPort); + return ERR_CODE(DEV_OPEN_ERR); + } + + // 创建lambda回调函数,捕获this指针 + FunTCPServerRecv recvCallback = [this](const TCPClient* pClient, const char* pData, const unsigned int nLen) { + this->onTcpDataReceivedFromCallback(pClient, pData, nLen); + }; + + // 启动TCP服务器,使用lambda回调函数 + if (!m_pTcpServer->Start(recvCallback, false)) { + LOG_ERROR("Failed to start VrNets TCP server\n"); + return ERR_CODE(DEV_OPEN_ERR); + } + + m_port = nPort; + LOG_INFO("VrNets TCP server started successfully on port %d\n", nPort); + return SUCCESS; +} + +bool GrabBagPresenter::startServer(quint16 port) +{ + // 这个方法现在由InitTcpServer处理 + return InitTcpServer(port) == SUCCESS; +} + +void GrabBagPresenter::stopServer() +{ + if (!m_pTcpServer) { + return; + } + + m_pTcpServer->Stop(); + m_port = 0; + LOG_INFO("VrNets TCP server stopped\n"); +} + +// TCP服务器数据接收回调处理方法 +void GrabBagPresenter::onTcpDataReceivedFromCallback(const TCPClient* pClient, const char* pData, const unsigned int nLen) +{ + if (!pClient || !pData || nLen == 0) { + return; + } + + std::string receivedData(pData, nLen); + QString qData = QString::fromStdString(receivedData).trimmed(); + + LOG_INFO("TCP data received from client %p: %s\n", pClient, qData.toStdString().c_str()); + + // 生成客户端ID用于标识 + QString clientId = QString("Client_%1").arg(reinterpret_cast(pClient)); + + if (m_pStatus) { + m_pStatus->OnStatusUpdate("收到TCP数据: " + qData.toStdString()); + } + + // 检查是否收到"Trig"命令 + if (qData.contains("Trig")) { + LOG_INFO("Received Trig command, starting camera 1 detection\n"); + + if (m_pStatus) { + m_pStatus->OnStatusUpdate("收到触发命令,启动检测"); + } + + // 启动第一个相机检测 + int result = StartDetection(1, false); // 启动相机1,非自动模式 + + // 发送响应给客户端 + QString response; + if (result != SUCCESS) { + response = QString("Error_%1").arg(result); + LOG_ERROR("Failed to start camera 1 detection, error: %d\n", result); + } + // 向客户端发送响应 + m_pTcpServer->SendData(pClient, response.toStdString().c_str(), response.toStdString().length()); + } else { + LOG_WARNING("Unknown TCP command received: %s\n", qData.toStdString().c_str()); + if (m_pStatus) { + m_pStatus->OnStatusUpdate("未知TCP命令: " + qData.toStdString()); + } + + // 发送错误响应 + std::string err = "Unknown_Command"; + m_pTcpServer->SendData(pClient, err.c_str(), err.length()); + } +} + // 初始化算法参数 int GrabBagPresenter::InitAlgorithmParams() { @@ -1161,6 +1286,27 @@ void GrabBagPresenter::_SendDetectionResultToRobot(const DetectionResult& detect } else { LOG_WARNING("Serial protocol not available\n"); } + + // 发送给TCP服务端 + bool tcpipSent = false; + if(m_pTcpServer){ + if(!multiTargetData.targets.empty()){ + std::string result = ""; + result.append("1,"); + result.append(std::to_string(multiTargetData.targets[0].x)); + result.append(","); + result.append(std::to_string(multiTargetData.targets[0].y)); + result.append(","); + result.append(std::to_string(multiTargetData.targets[0].z)); + result.append(","); + result.append(std::to_string(multiTargetData.targets[0].rz)); + m_pTcpServer->SendAllData(result.c_str(), result.length()); + }else { + std::string err = "0"; + m_pTcpServer->SendAllData(err.c_str(), err.length()); + } + } + // 总结发送状态 if (robotSent || serialSent) { diff --git a/GrabBagApp/Presenter/Src/RobotProtocol.cpp b/App/GrabBag/GrabBagApp/Presenter/Src/RobotProtocol.cpp similarity index 100% rename from GrabBagApp/Presenter/Src/RobotProtocol.cpp rename to App/GrabBag/GrabBagApp/Presenter/Src/RobotProtocol.cpp diff --git a/GrabBagApp/Presenter/Src/SerialProtocol.cpp b/App/GrabBag/GrabBagApp/Presenter/Src/SerialProtocol.cpp similarity index 100% rename from GrabBagApp/Presenter/Src/SerialProtocol.cpp rename to App/GrabBag/GrabBagApp/Presenter/Src/SerialProtocol.cpp diff --git a/GrabBagApp/Utils/Inc/LaserDataLoader.h b/App/GrabBag/GrabBagApp/Utils/Inc/LaserDataLoader.h similarity index 100% rename from GrabBagApp/Utils/Inc/LaserDataLoader.h rename to App/GrabBag/GrabBagApp/Utils/Inc/LaserDataLoader.h diff --git a/GrabBagApp/Utils/Inc/PathManager.h b/App/GrabBag/GrabBagApp/Utils/Inc/PathManager.h similarity index 100% rename from GrabBagApp/Utils/Inc/PathManager.h rename to App/GrabBag/GrabBagApp/Utils/Inc/PathManager.h diff --git a/GrabBagApp/Utils/Inc/PointCloudImageUtils.h b/App/GrabBag/GrabBagApp/Utils/Inc/PointCloudImageUtils.h similarity index 100% rename from GrabBagApp/Utils/Inc/PointCloudImageUtils.h rename to App/GrabBag/GrabBagApp/Utils/Inc/PointCloudImageUtils.h diff --git a/GrabBagApp/Utils/Src/LaserDataLoader.cpp b/App/GrabBag/GrabBagApp/Utils/Src/LaserDataLoader.cpp similarity index 100% rename from GrabBagApp/Utils/Src/LaserDataLoader.cpp rename to App/GrabBag/GrabBagApp/Utils/Src/LaserDataLoader.cpp diff --git a/GrabBagApp/Utils/Src/PathManager.cpp b/App/GrabBag/GrabBagApp/Utils/Src/PathManager.cpp similarity index 100% rename from GrabBagApp/Utils/Src/PathManager.cpp rename to App/GrabBag/GrabBagApp/Utils/Src/PathManager.cpp diff --git a/GrabBagApp/Utils/Src/PointCloudImageUtils.cpp b/App/GrabBag/GrabBagApp/Utils/Src/PointCloudImageUtils.cpp similarity index 100% rename from GrabBagApp/Utils/Src/PointCloudImageUtils.cpp rename to App/GrabBag/GrabBagApp/Utils/Src/PointCloudImageUtils.cpp diff --git a/GrabBagApp/Version.h b/App/GrabBag/GrabBagApp/Version.h similarity index 70% rename from GrabBagApp/Version.h rename to App/GrabBag/GrabBagApp/Version.h index 6707ecd..572bd75 100644 --- a/GrabBagApp/Version.h +++ b/App/GrabBag/GrabBagApp/Version.h @@ -2,9 +2,9 @@ #define VERSION_H -#define GRABBAG_VERSION_STRING "1.1.3" -#define GRABBAG_BUILD_STRING "1" -#define GRABBAG_FULL_VERSION_STRING "V1.1.3_1" +#define GRABBAG_VERSION_STRING "1.1.4" +#define GRABBAG_BUILD_STRING "2" +#define GRABBAG_FULL_VERSION_STRING "V1.1.4_2" // 获取版本信息的便捷函数 inline const char* GetGrabBagVersion() { diff --git a/GrabBagPrj/Version/Version.md b/App/GrabBag/GrabBagApp/Version.md similarity index 90% rename from GrabBagPrj/Version/Version.md rename to App/GrabBag/GrabBagApp/Version.md index abc4ea0..701ba78 100644 --- a/GrabBagPrj/Version/Version.md +++ b/App/GrabBag/GrabBagApp/Version.md @@ -1,3 +1,10 @@ +#1.1.4 2025-09-13 +## build_2 +1. 启动图标修复 + +## build_1 +1. 增加TCP通信接口 + # 1.1.3 2025-8-17 ## build_1 1. 发现编织袋没有调用矫正接口 diff --git a/GrabBagApp/devstatus.cpp b/App/GrabBag/GrabBagApp/devstatus.cpp similarity index 100% rename from GrabBagApp/devstatus.cpp rename to App/GrabBag/GrabBagApp/devstatus.cpp diff --git a/GrabBagApp/devstatus.h b/App/GrabBag/GrabBagApp/devstatus.h similarity index 100% rename from GrabBagApp/devstatus.h rename to App/GrabBag/GrabBagApp/devstatus.h diff --git a/GrabBagApp/devstatus.ui b/App/GrabBag/GrabBagApp/devstatus.ui similarity index 100% rename from GrabBagApp/devstatus.ui rename to App/GrabBag/GrabBagApp/devstatus.ui diff --git a/GrabBagApp/dialogcamera.cpp b/App/GrabBag/GrabBagApp/dialogcamera.cpp similarity index 100% rename from GrabBagApp/dialogcamera.cpp rename to App/GrabBag/GrabBagApp/dialogcamera.cpp diff --git a/GrabBagApp/dialogcamera.h b/App/GrabBag/GrabBagApp/dialogcamera.h similarity index 100% rename from GrabBagApp/dialogcamera.h rename to App/GrabBag/GrabBagApp/dialogcamera.h diff --git a/GrabBagApp/dialogcamera.ui b/App/GrabBag/GrabBagApp/dialogcamera.ui similarity index 100% rename from GrabBagApp/dialogcamera.ui rename to App/GrabBag/GrabBagApp/dialogcamera.ui diff --git a/GrabBagApp/dialogcameralevel.cpp b/App/GrabBag/GrabBagApp/dialogcameralevel.cpp similarity index 100% rename from GrabBagApp/dialogcameralevel.cpp rename to App/GrabBag/GrabBagApp/dialogcameralevel.cpp diff --git a/GrabBagApp/dialogcameralevel.h b/App/GrabBag/GrabBagApp/dialogcameralevel.h similarity index 100% rename from GrabBagApp/dialogcameralevel.h rename to App/GrabBag/GrabBagApp/dialogcameralevel.h diff --git a/GrabBagApp/dialogcameralevel.ui b/App/GrabBag/GrabBagApp/dialogcameralevel.ui similarity index 100% rename from GrabBagApp/dialogcameralevel.ui rename to App/GrabBag/GrabBagApp/dialogcameralevel.ui diff --git a/GrabBagApp/dialogconfig.cpp b/App/GrabBag/GrabBagApp/dialogconfig.cpp similarity index 100% rename from GrabBagApp/dialogconfig.cpp rename to App/GrabBag/GrabBagApp/dialogconfig.cpp diff --git a/GrabBagApp/dialogconfig.h b/App/GrabBag/GrabBagApp/dialogconfig.h similarity index 100% rename from GrabBagApp/dialogconfig.h rename to App/GrabBag/GrabBagApp/dialogconfig.h diff --git a/GrabBagApp/dialogconfig.ui b/App/GrabBag/GrabBagApp/dialogconfig.ui similarity index 100% rename from GrabBagApp/dialogconfig.ui rename to App/GrabBag/GrabBagApp/dialogconfig.ui diff --git a/GrabBagApp/main.cpp b/App/GrabBag/GrabBagApp/main.cpp similarity index 100% rename from GrabBagApp/main.cpp rename to App/GrabBag/GrabBagApp/main.cpp diff --git a/GrabBagApp/mainwindow.cpp b/App/GrabBag/GrabBagApp/mainwindow.cpp similarity index 100% rename from GrabBagApp/mainwindow.cpp rename to App/GrabBag/GrabBagApp/mainwindow.cpp diff --git a/GrabBagApp/mainwindow.h b/App/GrabBag/GrabBagApp/mainwindow.h similarity index 100% rename from GrabBagApp/mainwindow.h rename to App/GrabBag/GrabBagApp/mainwindow.h diff --git a/GrabBagApp/mainwindow.ui b/App/GrabBag/GrabBagApp/mainwindow.ui similarity index 100% rename from GrabBagApp/mainwindow.ui rename to App/GrabBag/GrabBagApp/mainwindow.ui diff --git a/GrabBagApp/resource/camera_offline.png b/App/GrabBag/GrabBagApp/resource/camera_offline.png similarity index 100% rename from GrabBagApp/resource/camera_offline.png rename to App/GrabBag/GrabBagApp/resource/camera_offline.png diff --git a/GrabBagApp/resource/camera_online.png b/App/GrabBag/GrabBagApp/resource/camera_online.png similarity index 100% rename from GrabBagApp/resource/camera_online.png rename to App/GrabBag/GrabBagApp/resource/camera_online.png diff --git a/GrabBagApp/resource/close.png b/App/GrabBag/GrabBagApp/resource/close.png similarity index 100% rename from GrabBagApp/resource/close.png rename to App/GrabBag/GrabBagApp/resource/close.png diff --git a/GrabBagApp/resource/config_algo.png b/App/GrabBag/GrabBagApp/resource/config_algo.png similarity index 100% rename from GrabBagApp/resource/config_algo.png rename to App/GrabBag/GrabBagApp/resource/config_algo.png diff --git a/GrabBagApp/resource/config_algo_s.png b/App/GrabBag/GrabBagApp/resource/config_algo_s.png similarity index 100% rename from GrabBagApp/resource/config_algo_s.png rename to App/GrabBag/GrabBagApp/resource/config_algo_s.png diff --git a/GrabBagApp/resource/config_camera.png b/App/GrabBag/GrabBagApp/resource/config_camera.png similarity index 100% rename from GrabBagApp/resource/config_camera.png rename to App/GrabBag/GrabBagApp/resource/config_camera.png diff --git a/GrabBagApp/resource/config_camera_level.png b/App/GrabBag/GrabBagApp/resource/config_camera_level.png similarity index 100% rename from GrabBagApp/resource/config_camera_level.png rename to App/GrabBag/GrabBagApp/resource/config_camera_level.png diff --git a/GrabBagApp/resource/config_camera_level_s.png b/App/GrabBag/GrabBagApp/resource/config_camera_level_s.png similarity index 100% rename from GrabBagApp/resource/config_camera_level_s.png rename to App/GrabBag/GrabBagApp/resource/config_camera_level_s.png diff --git a/GrabBagApp/resource/config_camera_s.png b/App/GrabBag/GrabBagApp/resource/config_camera_s.png similarity index 100% rename from GrabBagApp/resource/config_camera_s.png rename to App/GrabBag/GrabBagApp/resource/config_camera_s.png diff --git a/GrabBagApp/resource/config_data_test.png b/App/GrabBag/GrabBagApp/resource/config_data_test.png similarity index 100% rename from GrabBagApp/resource/config_data_test.png rename to App/GrabBag/GrabBagApp/resource/config_data_test.png diff --git a/GrabBagApp/resource/config_data_test.svg b/App/GrabBag/GrabBagApp/resource/config_data_test.svg similarity index 100% rename from GrabBagApp/resource/config_data_test.svg rename to App/GrabBag/GrabBagApp/resource/config_data_test.svg diff --git a/GrabBagApp/resource/config_data_test_s.png b/App/GrabBag/GrabBagApp/resource/config_data_test_s.png similarity index 100% rename from GrabBagApp/resource/config_data_test_s.png rename to App/GrabBag/GrabBagApp/resource/config_data_test_s.png diff --git a/GrabBagApp/resource/config_model.png b/App/GrabBag/GrabBagApp/resource/config_model.png similarity index 100% rename from GrabBagApp/resource/config_model.png rename to App/GrabBag/GrabBagApp/resource/config_model.png diff --git a/GrabBagApp/resource/dialog_cancel.png b/App/GrabBag/GrabBagApp/resource/dialog_cancel.png similarity index 100% rename from GrabBagApp/resource/dialog_cancel.png rename to App/GrabBag/GrabBagApp/resource/dialog_cancel.png diff --git a/GrabBagApp/resource/dialog_ok.png b/App/GrabBag/GrabBagApp/resource/dialog_ok.png similarity index 100% rename from GrabBagApp/resource/dialog_ok.png rename to App/GrabBag/GrabBagApp/resource/dialog_ok.png diff --git a/GrabBagApp/resource/hide.png b/App/GrabBag/GrabBagApp/resource/hide.png similarity index 100% rename from GrabBagApp/resource/hide.png rename to App/GrabBag/GrabBagApp/resource/hide.png diff --git a/GrabBagApp/resource/logo.ico b/App/GrabBag/GrabBagApp/resource/logo.ico similarity index 100% rename from GrabBagApp/resource/logo.ico rename to App/GrabBag/GrabBagApp/resource/logo.ico diff --git a/GrabBagApp/resource/logo.png b/App/GrabBag/GrabBagApp/resource/logo.png similarity index 100% rename from GrabBagApp/resource/logo.png rename to App/GrabBag/GrabBagApp/resource/logo.png diff --git a/GrabBagApp/resource/result_icon.png b/App/GrabBag/GrabBagApp/resource/result_icon.png similarity index 100% rename from GrabBagApp/resource/result_icon.png rename to App/GrabBag/GrabBagApp/resource/result_icon.png diff --git a/GrabBagApp/resource/robot_offline.png b/App/GrabBag/GrabBagApp/resource/robot_offline.png similarity index 100% rename from GrabBagApp/resource/robot_offline.png rename to App/GrabBag/GrabBagApp/resource/robot_offline.png diff --git a/GrabBagApp/resource/robot_online.png b/App/GrabBag/GrabBagApp/resource/robot_online.png similarity index 100% rename from GrabBagApp/resource/robot_online.png rename to App/GrabBag/GrabBagApp/resource/robot_online.png diff --git a/GrabBagApp/resource/start.png b/App/GrabBag/GrabBagApp/resource/start.png similarity index 100% rename from GrabBagApp/resource/start.png rename to App/GrabBag/GrabBagApp/resource/start.png diff --git a/GrabBagApp/resource/stop.png b/App/GrabBag/GrabBagApp/resource/stop.png similarity index 100% rename from GrabBagApp/resource/stop.png rename to App/GrabBag/GrabBagApp/resource/stop.png diff --git a/GrabBagApp/resources.qrc b/App/GrabBag/GrabBagApp/resources.qrc similarity index 100% rename from GrabBagApp/resources.qrc rename to App/GrabBag/GrabBagApp/resources.qrc diff --git a/GrabBagApp/resultitem.cpp b/App/GrabBag/GrabBagApp/resultitem.cpp similarity index 100% rename from GrabBagApp/resultitem.cpp rename to App/GrabBag/GrabBagApp/resultitem.cpp diff --git a/GrabBagApp/resultitem.h b/App/GrabBag/GrabBagApp/resultitem.h similarity index 100% rename from GrabBagApp/resultitem.h rename to App/GrabBag/GrabBagApp/resultitem.h diff --git a/GrabBagApp/resultitem.ui b/App/GrabBag/GrabBagApp/resultitem.ui similarity index 100% rename from GrabBagApp/resultitem.ui rename to App/GrabBag/GrabBagApp/resultitem.ui diff --git a/GrabBagConfig/CMakeLists.txt b/App/GrabBag/GrabBagConfig/CMakeLists.txt similarity index 100% rename from GrabBagConfig/CMakeLists.txt rename to App/GrabBag/GrabBagConfig/CMakeLists.txt diff --git a/GrabBagConfig/GrabBagConfig.pro b/App/GrabBag/GrabBagConfig/GrabBagConfig.pro similarity index 68% rename from GrabBagConfig/GrabBagConfig.pro rename to App/GrabBag/GrabBagConfig/GrabBagConfig.pro index 791bd04..a197061 100644 --- a/GrabBagConfig/GrabBagConfig.pro +++ b/App/GrabBag/GrabBagConfig/GrabBagConfig.pro @@ -20,20 +20,20 @@ HEADERS += \ _Inc/VrConfig.h #utils -INCLUDEPATH += $$PWD/../VrUtils/Inc -INCLUDEPATH += $$PWD/../VrUtils/tinyxml2 +INCLUDEPATH += $$PWD/../../../VrUtils/Inc +INCLUDEPATH += $$PWD/../../../VrUtils/tinyxml2 win32:CONFIG(debug, debug|release) { - LIBS += -L../VrUtils/Debug -lVrUtils + LIBS += -L../../../VrUtils/Debug -lVrUtils }else:win32:CONFIG(release, debug|release){ - LIBS += -L../VrUtils/release -lVrUtils + LIBS += -L../../../VrUtils/release -lVrUtils }else:unix:!macx { # Unix/Linux平台库链接(including cross-compilation) - LIBS += -L../VrUtils -lVrUtils + LIBS += -L../../../VrUtils -lVrUtils } # Default rules for deployment. unix { target.path = /usr/lib } -!isEmpty(target.path): INSTALLS += target \ No newline at end of file +!isEmpty(target.path): INSTALLS += target diff --git a/GrabBagConfig/Inc/IVrConfig.h b/App/GrabBag/GrabBagConfig/Inc/IVrConfig.h similarity index 93% rename from GrabBagConfig/Inc/IVrConfig.h rename to App/GrabBag/GrabBagConfig/Inc/IVrConfig.h index 4803789..893b0f6 100644 --- a/GrabBagConfig/Inc/IVrConfig.h +++ b/App/GrabBag/GrabBagConfig/Inc/IVrConfig.h @@ -68,6 +68,15 @@ struct SerialConfig bool enabled = true; // 是否启用串口通信 }; +/** + * @brief TCP服务器配置信息 + */ +struct TcpServerConfig +{ + std::string ipAddress = "127.0.0.1"; // 服务器IP地址 + int port = 6800; // 服务器端口 +}; + /** * @brief 编织袋参数 */ @@ -279,7 +288,8 @@ struct ConfigResult VrAlgorithmParams algorithmParams; // 算法参数 VrDebugParam debugParam; // 调试参数 SerialConfig serialConfig; // 串口配置 - ProjectType projectType; // 项目类型 + TcpServerConfig tcpServerConfig; // TCP服务器配置 + ProjectType projectType = ProjectType::GrabBag; // 项目类型 }; /** diff --git a/GrabBagConfig/Src/VrConfig.cpp b/App/GrabBag/GrabBagConfig/Src/VrConfig.cpp similarity index 95% rename from GrabBagConfig/Src/VrConfig.cpp rename to App/GrabBag/GrabBagConfig/Src/VrConfig.cpp index 50cf930..63d2c0e 100644 --- a/GrabBagConfig/Src/VrConfig.cpp +++ b/App/GrabBag/GrabBagConfig/Src/VrConfig.cpp @@ -395,6 +395,19 @@ ConfigResult CVrConfig::LoadConfig(const std::string& filePath) result.serialConfig.enabled = serialConfigElement->BoolAttribute("enabled"); } + // 解析TCP服务器配置 + XMLElement* tcpServerConfigElement = root->FirstChildElement("TcpServerConfig"); + if (tcpServerConfigElement) + { + tcpServerConfigElement->FirstChildElement("IpAddress") ? + result.tcpServerConfig.ipAddress = tcpServerConfigElement->FirstChildElement("IpAddress")->GetText() : + result.tcpServerConfig.ipAddress = "127.0.0.1"; + + tcpServerConfigElement->FirstChildElement("Port") ? + result.tcpServerConfig.port = tcpServerConfigElement->FirstChildElement("Port")->IntText() : + result.tcpServerConfig.port = 6800; + } + return result; } @@ -579,7 +592,7 @@ bool CVrConfig::SaveConfig(const std::string& filePath, ConfigResult& configResu debugParamElement->SetAttribute("debugOutputPath", configResult.debugParam.debugOutputPath.c_str()); root->InsertEndChild(debugParamElement); - // 添加串口配置 + // 串口配置 XMLElement* serialConfigElement = doc.NewElement("SerialConfig"); serialConfigElement->SetAttribute("portName", configResult.serialConfig.portName.c_str()); serialConfigElement->SetAttribute("baudRate", configResult.serialConfig.baudRate); @@ -590,6 +603,18 @@ bool CVrConfig::SaveConfig(const std::string& filePath, ConfigResult& configResu serialConfigElement->SetAttribute("enabled", configResult.serialConfig.enabled); root->InsertEndChild(serialConfigElement); + // TCP服务器配置 + XMLElement* tcpServerConfigElement = doc.NewElement("TcpServerConfig"); + root->InsertEndChild(tcpServerConfigElement); + + XMLElement* ipAddressElement = doc.NewElement("IpAddress"); + ipAddressElement->SetText(configResult.tcpServerConfig.ipAddress.c_str()); + tcpServerConfigElement->InsertEndChild(ipAddressElement); + + XMLElement* portElement = doc.NewElement("Port"); + portElement->SetText(configResult.tcpServerConfig.port); + tcpServerConfigElement->InsertEndChild(portElement); + // 保存到文件 XMLError err = doc.SaveFile(filePath.c_str()); if (err != XML_SUCCESS) diff --git a/GrabBagConfig/_Inc/VrConfig.h b/App/GrabBag/GrabBagConfig/_Inc/VrConfig.h similarity index 100% rename from GrabBagConfig/_Inc/VrConfig.h rename to App/GrabBag/GrabBagConfig/_Inc/VrConfig.h diff --git a/GrabBagConfig/config/EyeHandCalibMatrixInfo.ini b/App/GrabBag/GrabBagConfig/config/EyeHandCalibMatrixInfo.ini similarity index 100% rename from GrabBagConfig/config/EyeHandCalibMatrixInfo.ini rename to App/GrabBag/GrabBagConfig/config/EyeHandCalibMatrixInfo.ini diff --git a/GrabBagConfig/config/config.xml b/App/GrabBag/GrabBagConfig/config/config.xml similarity index 96% rename from GrabBagConfig/config/config.xml rename to App/GrabBag/GrabBagConfig/config/config.xml index 49daceb..db2fbac 100644 --- a/GrabBagConfig/config/config.xml +++ b/App/GrabBag/GrabBagConfig/config/config.xml @@ -76,4 +76,10 @@ + + + + 127.0.0.1 + 6800 + \ No newline at end of file diff --git a/GrabBagConfigCmd/ConfigCmdParser.cpp b/App/GrabBag/GrabBagConfigCmd/ConfigCmdParser.cpp similarity index 100% rename from GrabBagConfigCmd/ConfigCmdParser.cpp rename to App/GrabBag/GrabBagConfigCmd/ConfigCmdParser.cpp diff --git a/GrabBagConfigCmd/ConfigCmdParser.h b/App/GrabBag/GrabBagConfigCmd/ConfigCmdParser.h similarity index 100% rename from GrabBagConfigCmd/ConfigCmdParser.h rename to App/GrabBag/GrabBagConfigCmd/ConfigCmdParser.h diff --git a/GrabBagConfigCmd/GrabBagConfigCmd.pro b/App/GrabBag/GrabBagConfigCmd/GrabBagConfigCmd.pro similarity index 100% rename from GrabBagConfigCmd/GrabBagConfigCmd.pro rename to App/GrabBag/GrabBagConfigCmd/GrabBagConfigCmd.pro diff --git a/GrabBagConfigCmd/main.cpp b/App/GrabBag/GrabBagConfigCmd/main.cpp similarity index 100% rename from GrabBagConfigCmd/main.cpp rename to App/GrabBag/GrabBagConfigCmd/main.cpp diff --git a/GrabBagConfigCmd/使用示例.md b/App/GrabBag/GrabBagConfigCmd/使用示例.md similarity index 100% rename from GrabBagConfigCmd/使用示例.md rename to App/GrabBag/GrabBagConfigCmd/使用示例.md diff --git a/App/LapWeld/LapWeld.pro b/App/LapWeld/LapWeld.pro new file mode 100644 index 0000000..e302077 --- /dev/null +++ b/App/LapWeld/LapWeld.pro @@ -0,0 +1,4 @@ +TEMPLATE = subdirs +# 拆包项目 +SUBDIRS += LapWeldApp/LapWeldApp.pro +SUBDIRS += LapWeldConfig/LapWeldConfig.pro diff --git a/App/LapWeld/LapWeldApp/IYLapWeldStatus.h b/App/LapWeld/LapWeldApp/IYLapWeldStatus.h new file mode 100644 index 0000000..efff5e1 --- /dev/null +++ b/App/LapWeld/LapWeldApp/IYLapWeldStatus.h @@ -0,0 +1,91 @@ +#ifndef IYLAPWELDSTATUS_H +#define IYLAPWELDSTATUS_H + +#include +#include +#include +#include +#include + +// 工作状态枚举 +enum class WorkStatus { + InitIng, // 初始化中 + Ready, // 准备就绪 + Working, // 正在工作 + Completed, // 监测完成 + Error // 设备异常 +}; + +// 工作状态转换为字符串的工具函数 +inline std::string WorkStatusToString(WorkStatus status) { + switch (status) { + case WorkStatus::InitIng: return "初始化中"; + case WorkStatus::Ready: return "准备就绪"; + case WorkStatus::Working: return "正在工作"; + case WorkStatus::Completed: return "检测完成"; + case WorkStatus::Error: return "设备异常"; + default: return "未知状态"; + } +} + +// 坐标结构体 +struct LapWeldPosition { + double x; + double y; + double z; + double roll; + double pitch; + double yaw; + + // 默认构造函数 + LapWeldPosition() : x(0), y(0), z(0), roll(0), pitch(0), yaw(0) {} + + // 拷贝构造函数(编译器会自动生成,但为了明确可以保留) + LapWeldPosition(const LapWeldPosition&) = default; + LapWeldPosition& operator=(const LapWeldPosition&) = default; +}; + + + +// 检测结果结构体 +struct DetectionResult { + QImage image; + std::vector positions; + int cameraIndex = 1; // 相机索引,默认为1(第一个相机) +}; + +// 状态回调接口 +class IYLapWeldStatus +{ +public: + virtual ~IYLapWeldStatus() = default; + + // 状态文字回调 + virtual void OnStatusUpdate(const std::string& statusMessage) = 0; + + // 算法监测结果回调 + virtual void OnDetectionResult(const DetectionResult& result) = 0; + + // 相机状态回调 + virtual void OnCamera1StatusChanged(bool isConnected) = 0; + virtual void OnCamera2StatusChanged(bool isConnected) = 0; + + // 机械臂连接状态回调 + virtual void OnRobotConnectionChanged(bool isConnected) = 0; + + // 串口连接状态回调 + virtual void OnSerialConnectionChanged(bool isConnected) = 0; + + // 相机个数回调 + virtual void OnCameraCountChanged(int cameraCount) = 0; + + // 工作状态回调 + virtual void OnWorkStatusChanged(WorkStatus status) = 0; +}; + +// 声明Qt元类型,使这些结构体能够在信号槽中传递 +Q_DECLARE_METATYPE(WorkStatus) +Q_DECLARE_METATYPE(LapWeldPosition) +Q_DECLARE_METATYPE(DetectionResult) + +#endif // IYLAPWELDSTATUS_H diff --git a/GrabBagApp/GrabBagApp.pro b/App/LapWeld/LapWeldApp/LapWeldApp.pro similarity index 53% rename from GrabBagApp/GrabBagApp.pro rename to App/LapWeld/LapWeldApp/LapWeldApp.pro index 9f31b5f..2137b54 100644 --- a/GrabBagApp/GrabBagApp.pro +++ b/App/LapWeld/LapWeldApp/LapWeldApp.pro @@ -5,7 +5,7 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TEMPLATE = app -CONFIG += c++17 +CONFIG += c++11 # Add /utf-8 flag only for MSVC builds to enforce UTF-8 encoding win32-msvc { QMAKE_CXXFLAGS += /utf-8 @@ -16,23 +16,24 @@ win32-msvc { # In order to do so, uncomment the following line. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + # 设置应用程序图标 RC_ICONS = resource/logo.ico - -INCLUDEPATH += $$PWD/../VrCommon/Inc -INCLUDEPATH += $$PWD/../VrUtils/Inc INCLUDEPATH += $$PWD/Presenter/Inc INCLUDEPATH += $$PWD/Utils/Inc -INCLUDEPATH += $$PWD/../Module/ModbusTCPServer/Inc -INCLUDEPATH += $$PWD/../Module/ShareMem/Inc +INCLUDEPATH += $$PWD/../../../VrCommon/Inc +INCLUDEPATH += $$PWD/../../../VrUtils/Inc -INCLUDEPATH += $$PWD/../GrabBagConfig/Inc -INCLUDEPATH += $$PWD/../VrEyeDevice/Inc +INCLUDEPATH += $$PWD/../../../Module/ModbusTCPServer/Inc +INCLUDEPATH += $$PWD/../../../Module/ShareMem/Inc + +INCLUDEPATH += $$PWD/../LapWeldConfig/Inc +INCLUDEPATH += $$PWD/../../../VrEyeDevice/Inc SOURCES += \ Presenter/Src/DetectPresenter.cpp \ - Presenter/Src/GrabBagPresenter.cpp \ + Presenter/Src/LapWeldPresenter.cpp \ Presenter/Src/RobotProtocol.cpp \ Presenter/Src/SerialProtocol.cpp \ Presenter/Src/ConfigManager.cpp \ @@ -42,27 +43,25 @@ SOURCES += \ devstatus.cpp \ dialogcamera.cpp \ dialogcameralevel.cpp \ - dialogconfig.cpp \ main.cpp \ mainwindow.cpp \ resultitem.cpp HEADERS += \ Presenter/Inc/DetectPresenter.h \ - Presenter/Inc/GrabBagPresenter.h \ + Presenter/Inc/LapWeldPresenter.h \ Presenter/Inc/ProtocolCommon.h \ Presenter/Inc/RobotProtocol.h \ Presenter/Inc/SerialProtocol.h \ Presenter/Inc/ConfigManager.h \ Utils/Inc/LaserDataLoader.h \ Utils/Inc/PathManager.h \ - IYGrabBagStatus.h \ + IYLapWeldStatus.h \ Utils/Inc/PointCloudImageUtils.h \ Version.h \ devstatus.h \ dialogcamera.h \ dialogcameralevel.h \ - dialogconfig.h \ mainwindow.h \ resultitem.h @@ -70,7 +69,6 @@ FORMS += \ devstatus.ui \ dialogcamera.ui \ dialogcameralevel.ui \ - dialogconfig.ui \ mainwindow.ui \ resultitem.ui @@ -79,28 +77,28 @@ RESOURCES += \ win32:CONFIG(debug, debug|release) { - LIBS += -L../VrUtils/debug -lVrUtils - LIBS += -L../GrabBagConfig/debug -lGrabBagConfig - LIBS += -L../VrEyeDevice/debug -lVrEyeDevice - LIBS += -L../Module/ModbusTCPServer/debug -lModbusTCPServer - LIBS += -L../Module/ShareMem/debug -lShareMem - LIBS += -L../VrNets/debug -lVrModbus + LIBS += -L../../../VrUtils/debug -lVrUtils + LIBS += -L../LapWeldConfig/debug -lLapWeldConfig + LIBS += -L../../../VrEyeDevice/debug -lVrEyeDevice + LIBS += -L../../../Module/ModbusTCPServer/debug -lModbusTCPServer + LIBS += -L../../../Module/ShareMem/debug -lShareMem + LIBS += -L../../../VrNets/debug -lVrModbus }else:win32:CONFIG(release, debug|release){ - LIBS += -L../VrUtils/release -lVrUtils - LIBS += -L../GrabBagConfig/release -lGrabBagConfig - LIBS += -L../VrEyeDevice/release -lVrEyeDevice - LIBS += -L../Module/ModbusTCPServer/release -lModbusTCPServer - LIBS += -L../Module/ShareMem/release -lShareMem - LIBS += -L../VrNets/release -lVrModbus + LIBS += -L../../../VrUtils/release -lVrUtils + LIBS += -L../LapWeldConfig/release -lLapWeldConfig + LIBS += -L../../../VrEyeDevice/release -lVrEyeDevice + LIBS += -L../../../Module/ModbusTCPServer/release -lModbusTCPServer + LIBS += -L../../../Module/ShareMem/release -lShareMem + LIBS += -L../../../VrNets/release -lVrModbus }else:unix:!macx { # Unix/Linux平台库链接(包括交叉编译) # 注意链接顺序:依赖关系从高到低 - LIBS += -L../GrabBagConfig -lGrabBagConfig - LIBS += -L../VrEyeDevice -lVrEyeDevice - LIBS += -L../Module/ModbusTCPServer -lModbusTCPServer - LIBS += -L../VrNets -lVrModbus - LIBS += -L../Module/ShareMem -lShareMem - LIBS += -L../VrUtils -lVrUtils + LIBS += -L../LapWeldConfig -lLapWeldConfig + LIBS += -L../../../VrEyeDevice -lVrEyeDevice + LIBS += -L../../../Module/ModbusTCPServer -lModbusTCPServer + LIBS += -L../../../VrNets -lVrModbus + LIBS += -L../../../Module/ShareMem -lShareMem + LIBS += -L../../../VrUtils -lVrUtils # 添加系统库依赖 LIBS += -lpthread @@ -108,36 +106,37 @@ win32:CONFIG(debug, debug|release) { #linux下的为unix ,windows下用的win32 -INCLUDEPATH += ../SDK/VzNLSDK/_Inc -INCLUDEPATH += ../SDK/VzNLSDK/Inc +INCLUDEPATH += ../../../SDK/VzNLSDK/_Inc +INCLUDEPATH += ../../../SDK/VzNLSDK/Inc win32:CONFIG(release, debug|release): { - LIBS += -L$$PWD/../SDK/VzNLSDK/Windows/x64/Release + LIBS += -L$$PWD/../../../SDK/VzNLSDK/Windows/x64/Release LIBS += -lVzKernel -lVzNLDetect -lVzNLGraphics } else:win32:CONFIG(debug, debug|release): { - LIBS += -L$$PWD/../SDK/VzNLSDK/Windows/x64/Debug + LIBS += -L$$PWD/../../../SDK/VzNLSDK/Windows/x64/Debug LIBS += -lVzKerneld -lVzNLDetectd -lVzNLGraphicsd } else:unix:!macx: { - LIBS += -L$$PWD/../SDK/VzNLSDK/Arm/aarch64 + LIBS += -L$$PWD/../../../SDK/VzNLSDK/Arm/aarch64 LIBS += -lVzEyeSecurityLoader-shared -lVzKernel -lVzNLDetect -lVzNLGraphics } # 算法 -INCLUDEPATH += ../SDK/bagPosition/Inc +INCLUDEPATH += ../../../SDK/lapWeldDetection/Inc +INCLUDEPATH += ../../../SDK/OpenCV320/include win32:CONFIG(release, debug|release): { - LIBS += -L$$PWD/../SDK/bagPosition/Windows/x64/Release -lbagPositioning -lbaseAlgorithm - LIBS += -L$$PWD/../SDK/OpenCV320/Windows/vc14/Release -lopencv_world320 + LIBS += -L$$PWD/../../../SDK/lapWeldDetection/Windows/x64/Release -llapWeldDetection + LIBS += -L$$PWD/../../../SDK/OpenCV320/Windows/vc14/Release -lopencv_world320 } else:win32:CONFIG(debug, debug|release): { - LIBS += -L$$PWD/../SDK/bagPosition/Windows/x64/Debug -lbagPositioning -lbaseAlgorithm - LIBS += -L$$PWD/../SDK/OpenCV320/Windows/vc14/Release -lopencv_world320 + LIBS += -L$$PWD/../../../SDK/lapWeldDetection/Windows/x64/Debug -llapWeldDetection + LIBS += -L$$PWD/../../../SDK/OpenCV320/Windows/vc14/Release -lopencv_world320 } else:unix:!macx: { - LIBS += -L$$PWD/../SDK/bagPosition/Arm/aarch64 -lbagPositioning -lbaseAlgorithm + LIBS += -L$$PWD/../SDK/lapWeldDetection/Arm/aarch64 -llapWeldDetection LIBS += -L$$PWD/../SDK/OpenCV320/Arm/aarch64 -lopencv_core -lopencv_imgproc -lopencv_highgui } diff --git a/App/LapWeld/LapWeldApp/Presenter/Inc/ConfigManager.h b/App/LapWeld/LapWeldApp/Presenter/Inc/ConfigManager.h new file mode 100644 index 0000000..c6b7f6c --- /dev/null +++ b/App/LapWeld/LapWeldApp/Presenter/Inc/ConfigManager.h @@ -0,0 +1,157 @@ +#ifndef CONFIGMANAGER_H +#define CONFIGMANAGER_H + +#include "IVrConfig.h" +#include "VrConfigCmd.h" +#include "IVrShareMem.h" +#include +#include +#include +#include +#include +#include + +/** + * @brief 扩展的相机UI参数 + */ +struct CameraUIParam +{ + int cameraIndex = 1; // 相机索引 + double exposeTime = 100.0; // 曝光时间 (微秒) + double gain = 1.0; // 增益值 + double frameRate = 30.0; // 帧率 + double swingSpeed = 10.0; // 摆动速度 + double swingStartAngle = 0.0; // 开始角度 + double swingStopAngle = 180.0; // 结束角度 +}; + +/** + * @brief 完整的系统配置数据 + */ +struct SystemConfig +{ + ConfigResult configResult; // VrConfig配置结果 + std::vector cameraUIParams; // 相机UI参数 + + // 获取指定相机的UI参数 + CameraUIParam* GetCameraUIParam(int cameraIndex); + const CameraUIParam* GetCameraUIParam(int cameraIndex) const; + + // 设置或更新指定相机的UI参数 + void SetCameraUIParam(const CameraUIParam& param); + + // 确保指定相机的参数存在 + void EnsureCameraUIParam(int cameraIndex); +}; + +/** + * @brief 配置变化监听器接口 + */ +class IConfigChangeListener +{ +public: + virtual ~IConfigChangeListener() = default; + + /** + * @brief 配置变化通知 + * @param config 新的配置数据 + */ + virtual void OnSystemConfigChanged(const SystemConfig& config) = 0; + + /** + * @brief 相机参数变化通知 + * @param cameraIndex 相机索引 + * @param cameraParam 相机参数 + */ + virtual void OnCameraParamChanged(int cameraIndex, const CameraUIParam& cameraParam) = 0; + + /** + * @brief 算法参数变化通知 + * @param algorithmParams 算法参数 + */ + virtual void OnAlgorithmParamChanged(const VrAlgorithmParams& algorithmParams) = 0; +}; + +/** + * @brief 配置管理器类 + * 负责管理系统配置、监听共享内存变化、提供配置变化通知 + */ +class ConfigManager +{ +public: + ConfigManager(); + ~ConfigManager(); + + // 基本配置管理 + bool Initialize(const std::string& configFilePath = ""); + void Shutdown(); + + // 配置访问(线程安全) + SystemConfig GetConfig() const; + CameraUIParam GetCameraUIParam(int cameraIndex) const; + VrAlgorithmParams GetAlgorithmParams() const; + ConfigResult GetConfigResult() const; + + // 配置更新 + bool UpdateCameraUIParam(int cameraIndex, const CameraUIParam& param); + bool UpdateAlgorithmParams(const VrAlgorithmParams& params); + bool UpdateFullConfig(const SystemConfig& config); + + // 配置文件操作 + bool LoadConfigFromFile(const std::string& filePath); + bool SaveConfigToFile(const std::string& filePath); + + // 监听器管理 + void AddConfigChangeListener(std::shared_ptr listener); + void RemoveConfigChangeListener(std::shared_ptr listener); + + // 共享内存监听控制 + bool StartSharedMemoryMonitor(); + void StopSharedMemoryMonitor(); + +private: + // 配置数据 + mutable std::mutex m_configMutex; + SystemConfig m_systemConfig; + std::string m_configFilePath; + + // VrConfig实例 + IVrConfig* m_pVrConfig; + + // 共享内存监听 + IVrShareMem* m_pConfigCmdShareMem; + std::thread m_sharedMemMonitorThread; + std::atomic m_bSharedMemMonitorRunning; + + // 监听器管理 + mutable std::mutex m_listenersMutex; + std::vector> m_listeners; + + // 内部方法 + void _SharedMemoryMonitorThread(); + bool _ApplyConfigCommand(const ConfigCmdData& configData); + bool _ApplyCameraExpose(const CameraConfigParam& param); + bool _ApplyCameraGain(const CameraConfigParam& param); + bool _ApplyCameraFrameRate(const CameraConfigParam& param); + bool _ApplyCameraSwing(const SwingConfigParam& param); + bool _ApplyAlgoParam(const AlgoConfigParam& param); + bool _ApplyCalibParam(const CalibConfigParam& param); + bool _ApplyFullConfig(const FullConfigParam& param); + + // 通知相关 + void _NotifyConfigChanged(); + void _NotifyCameraParamChanged(int cameraIndex); + void _NotifyAlgorithmParamChanged(); + void _CleanupExpiredListeners(); + + // 初始化相关 + bool _InitializeSharedMemory(); + void _CleanupSharedMemory(); + bool _InitializeDefaultConfig(); + + // JSON序列化/反序列化 + std::string _SerializeConfigToJson(const SystemConfig& config); + bool _DeserializeConfigFromJson(const std::string& json, SystemConfig& config); +}; + +#endif // CONFIGMANAGER_H \ No newline at end of file diff --git a/App/LapWeld/LapWeldApp/Presenter/Inc/DetectPresenter.h b/App/LapWeld/LapWeldApp/Presenter/Inc/DetectPresenter.h new file mode 100644 index 0000000..1a077b9 --- /dev/null +++ b/App/LapWeld/LapWeldApp/Presenter/Inc/DetectPresenter.h @@ -0,0 +1,48 @@ + +#pragma once +#include +#include +#include +#include "SX_lapWeldDetection_Export.h" +#include "VZNL_Types.h" +#include "VrTimeUtils.h" +#include "VrError.h" +#include "VrLog.h" +#include "IVrConfig.h" +#include "LaserDataLoader.h" +#include "IYLapWeldStatus.h" +#include "PointCloudImageUtils.h" +#include "VrConvert.h" +#include "VrDateUtils.h" + +class DetectPresenter +{ +private: + /* data */ +public: + DetectPresenter(/* args */); + ~DetectPresenter(); + + + int DetectLapWeld( std::vector& detectionDataCache, + const SSX_lapWeldParam& algoParam, + const SSG_planeCalibPara& cameraCalibParam, + const VrDebugParam& debugParam, + LaserDataLoader& dataLoader, + const double clibMatrix[16], + DetectionResult& detectionResult); + + + // 新增:处理统一数据格式和项目类型的DetectLapWeld函数 + int DetectLapWeld( int cameraIndex, + std::vector>& laserLines, + ProjectType projectType, + const SSX_lapWeldParam& algoParam, + const SSG_planeCalibPara& cameraCalibParam, + const VrDebugParam& debugParam, + LaserDataLoader& dataLoader, + const double clibMatrix[16], + DetectionResult& detectionResult); + +}; + diff --git a/App/LapWeld/LapWeldApp/Presenter/Inc/LapWeldPresenter.h b/App/LapWeld/LapWeldApp/Presenter/Inc/LapWeldPresenter.h new file mode 100644 index 0000000..1c97c61 --- /dev/null +++ b/App/LapWeld/LapWeldApp/Presenter/Inc/LapWeldPresenter.h @@ -0,0 +1,177 @@ +#ifndef LAPWELDPRESENTER_H +#define LAPWELDPRESENTER_H + +#include +#include + +#include "IVrConfig.h" +#include "IVrEyeDevice.h" +#include "IYLapWeldStatus.h" +#include "DetectPresenter.h" +#include "RobotProtocol.h" +#include "SerialProtocol.h" +#include "VZNL_Types.h" +#include "SX_lapWeldDetection_Export.h" +#include "SG_baseDataType.h" +#include "LaserDataLoader.h" +#include "PathManager.h" +#include "ConfigManager.h" +#include +#include +#include +#include + +class LapWeldPresenter : public IVrConfigChangeNotify, public IConfigChangeListener +{ +public: + LapWeldPresenter(); + ~LapWeldPresenter(); + + // 初始化 + int Init(); + + // 设置状态回调 + void SetStatusCallback(IYLapWeldStatus* status); + + // 开始检测 + int StartDetection(int cameraIndex = -1, bool isAuto = true); // cameraIndex: -1表示所有相机,1/2...表示特定相机 + + // 停止检测 + int StopDetection(); + + // 加载调试数据进行检测 + int LoadDebugDataAndDetect(const std::string& filePath); + + // 为所有相机设置状态回调 + void SetCameraStatusCallback(VzNL_OnNotifyStatusCBEx fNotify, void* param); + + // 设置默认相机索引 + void SetDefaultCameraIndex(int cameraIndex); + + // 获取当前默认相机索引 + int GetDefaultCameraIndex() const { return m_currentCameraIndex; } + + // 获取配置对象 + IVrConfig* GetConfig() { return m_vrConfig; } + + // 获取相机列表 + std::vector> GetCameraList() const { return m_vrEyeDeviceList; } // 返回相机列表,包括IP地址和设备指针 + + // 手眼标定矩阵管理 + const CalibMatrix GetClibMatrix(int index) const; + + // 实现IVrConfigChangeNotify接口 + virtual void OnConfigChanged(const ConfigResult& configResult) override; + + // 实现IConfigChangeListener接口 + virtual void OnSystemConfigChanged(const SystemConfig& config) override; + virtual void OnCameraParamChanged(int cameraIndex, const CameraUIParam& cameraParam) override; + virtual void OnAlgorithmParamChanged(const VrAlgorithmParams& algorithmParams) override; + + // 配置管理 + ConfigManager* GetConfigManager() { return m_pConfigManager.get(); } + + // 获取检测数据缓存的副本(用于保存数据) + int GetDetectIndex() const { return m_currentCameraIndex; } + + int GetDetectionDataCacheSize() const { return m_detectionDataCache.size(); } + + // 保存检测数据到文件(默认实现) + int SaveDetectionDataToFile(const std::string& filePath); + + + static void _StaticCameraNotify(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pInfoParam); + static void _StaticDetectionCallback(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint, void* pParam); +private: + + // 相机协议相关方法 + int InitCamera(std::vector& cameraList); + + // 机械臂协议相关方法 + int InitRobotProtocol(); + + // 串口协议相关方法 + int InitSerialProtocol(); + + // 算法初始化接口 + int InitAlgorithmParams(); + + // 机械臂协议回调函数 + void OnRobotConnectionChanged(bool connected); + bool OnRobotWorkSignal(bool startWork, int cameraIndex); + + // 串口协议回调函数 + void OnSerialConnectionChanged(bool connected); + bool OnSerialWorkSignal(bool startWork, int cameraIndex); + + // 连接状态检查和更新 + void CheckAndUpdateWorkStatus(); + + // 打开相机 + int _OpenDevice(int cameraIndex, const char* cameraName, const char* cameraIp, ProjectType& projectType); + + bool _SinglePreDetection(int cameraIndex); + int _SingleDetection(int cameraIndex, bool isStart); + + // 静态回调函数,供外部使用 + void _CameraNotify(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pInfoParam); + + // 检测数据回调函数 + void _DetectionCallback(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint, void* pParam); + + // 算法检测线程 + void _AlgoDetectThread(); + + int _DetectTask(); + + // 释放缓存的检测数据 + void _ClearDetectionDataCache(); + + // 发送检测结果给机械臂 + void _SendDetectionResultToRobot(const DetectionResult& detectionResult, int cameraIndex); + + // 根据相机索引获取调平参数 + SSG_planeCalibPara _GetCameraCalibParam(int cameraIndex); + +private: + IVrConfig* m_vrConfig = nullptr; + std::vector> m_vrEyeDeviceList; + IYLapWeldStatus* m_pStatus = nullptr; + DetectPresenter* m_pDetectPresenter = nullptr; + + RobotProtocol* m_pRobotProtocol = nullptr; + SerialProtocol* m_pSerialProtocol = nullptr; + + // 连接状态标志 + bool m_bCameraConnected = false; // 相机连接状态 + bool m_bRobotConnected = false; // 机械臂连接状态 + bool m_bSerialConnected = false; // 串口连接状态 + WorkStatus m_currentWorkStatus = WorkStatus::Error; // 当前工作状态 + int m_currentCameraIndex = 0; // 当前使用的相机编号 + + std::atomic m_bAlgoDetectThreadRunning = false; + std::mutex m_algoDetectMutex; + std::condition_variable m_algoDetectCondition; + std::thread m_algoDetectThread; // 算法检测线程 + + // 检测数据缓存 - 统一存储两种类型的数据 + std::mutex m_detectionDataMutex; + std::vector> m_detectionDataCache; // 统一存储数据 + + // 算法参数成员变量 + SSX_lapWeldParam m_algoParam; // 搭接焊缝算法参数 + SSG_planeCalibPara m_planeCalibParam; // 默认平面校准参数 + std::vector m_cameraCalibParams; // 多相机调平参数 + std::vector m_clibMatrixList; // 手眼标定矩阵列表 + VrDebugParam m_debugParam; // 调试参数 + + ProjectType m_projectType; + + // 调试数据加载器 + LaserDataLoader m_dataLoader; + + // 配置管理器 + std::unique_ptr m_pConfigManager; +}; + +#endif // LAPWELDPRESENTER_H diff --git a/App/LapWeld/LapWeldApp/Presenter/Inc/ProtocolCommon.h b/App/LapWeld/LapWeldApp/Presenter/Inc/ProtocolCommon.h new file mode 100644 index 0000000..b95c631 --- /dev/null +++ b/App/LapWeld/LapWeldApp/Presenter/Inc/ProtocolCommon.h @@ -0,0 +1,72 @@ +#ifndef PROTOCOLCOMMON_H +#define PROTOCOLCOMMON_H + +#include +#include +#include + +/** + * @brief 机械臂坐标结构体 + */ +struct RobotCoordinate { + float x; // X坐标 + float y; // Y坐标 + float z; // Z坐标 + float rx; // X轴旋转 + float ry; // Y轴旋转 + float rz; // Z轴旋转 +}; + +/** + * @brief 单个目标位置数据(简化版,只包含必要的x,y,z,rz) + */ +struct TargetPosition { + float x; // X坐标 + float y; // Y坐标 + float z; // Z坐标 + float rz; // Z轴旋转(yaw角) + uint16_t cameraId; // 相机ID(从1开始编号) +}; + +/** + * @brief 多目标检测结果数据结构 + */ +struct MultiTargetData { + uint16_t count; // 目标数量 + uint16_t cameraId; // 当前检测的相机ID(从1开始编号) + std::vector targets; // 目标位置列表 + + MultiTargetData() : count(0), cameraId(1) {} // 默认相机ID为1 +}; + +/** + * @brief 通用连接状态枚举 + */ +enum ConnectionStatus { + STATUS_DISCONNECTED = 0, // 断开连接 + STATUS_CONNECTED = 1, // 已连接 + STATUS_IDLE = 2, // 空闲状态 + STATUS_WORKING = 3, // 工作中 + STATUS_ERROR = 4 // 错误状态 +}; + +/** + * @brief 连接状态回调函数类型 + * @param connected true-连接,false-断开 + */ +using ConnectionCallback = std::function; + +/** + * @brief 开始工作信号回调函数类型 + * @param startWork true-开始工作,false-停止工作 + * @param cameraId 相机ID,从1开始编号(1,2,...) + */ +using WorkSignalCallback = std::function; + +// 工作状态定义(公共常量) +static const uint16_t WORK_STATUS_IDLE = 0; // 空闲 +static const uint16_t WORK_STATUS_CAMERA1_DONE = 1; // 相机1工作完成 +static const uint16_t WORK_STATUS_CAMERA2_DONE = 2; // 相机2工作完成 +static const uint16_t WORK_STATUS_BUSY = 3; // 忙碌 + +#endif // PROTOCOLCOMMON_H \ No newline at end of file diff --git a/App/LapWeld/LapWeldApp/Presenter/Inc/RobotProtocol.h b/App/LapWeld/LapWeldApp/Presenter/Inc/RobotProtocol.h new file mode 100644 index 0000000..75ee5b7 --- /dev/null +++ b/App/LapWeld/LapWeldApp/Presenter/Inc/RobotProtocol.h @@ -0,0 +1,140 @@ +#ifndef ROBOTPROTOCOL_H +#define ROBOTPROTOCOL_H + +#include +#include +#include "IYModbusTCPServer.h" +#include "ProtocolCommon.h" + + +/** + * @brief 机械臂ModbusTCP协议封装类 + * 提供机械臂坐标配置、状态检测、工作信号处理等功能 + */ +class RobotProtocol +{ + +public: + /** + * @brief 机械臂状态枚举(使用公共状态定义) + */ + using RobotStatus = ConnectionStatus; + +public: + RobotProtocol(); + ~RobotProtocol(); + + /** + * @brief 初始化ModbusTCP服务 + * @param port TCP端口号,默认502 + * @return 0-成功,其他-错误码 + */ + int Initialize(uint16_t port = 502); + + /** + * @brief 反初始化,停止服务 + */ + void Deinitialize(); + + /** + * @brief 设置多目标检测结果数据 + * @param multiTargetData 多目标数据结构 + * @param cameraId 相机ID(从1开始编号) + * @return 0-成功,其他-错误码 + */ + int SetMultiTargetData(const MultiTargetData& multiTargetData, uint16_t cameraId); + + + /** + * @brief 获取当前检测状态 + * @return 当前状态 + */ + RobotStatus GetDetectionStatus() const; + + /** + * @brief 设置连接状态回调 + * @param callback 回调函数 + */ + void SetConnectionCallback(const ConnectionCallback& callback); + + /** + * @brief 设置工作信号回调 + * @param callback 回调函数 + */ + void SetWorkSignalCallback(const WorkSignalCallback& callback); + + /** + * @brief 获取服务运行状态 + * @return true-运行中,false-已停止 + */ + bool IsRunning() const; + + /** + * @brief 设置工作状态 + * @param status 工作状态:0-空闲,1-相机1工作完成,2-相机2工作完成,3-忙碌 + * @return 0-成功,其他-错误码 + */ + int SetWorkStatus(uint16_t status); + + // 使用公共工作状态定义 + static const uint16_t WORK_STATUS_IDLE = ::WORK_STATUS_IDLE; + static const uint16_t WORK_STATUS_CAMERA1_DONE = ::WORK_STATUS_CAMERA1_DONE; + static const uint16_t WORK_STATUS_CAMERA2_DONE = ::WORK_STATUS_CAMERA2_DONE; + static const uint16_t WORK_STATUS_BUSY = ::WORK_STATUS_BUSY; + +private: + /** + * @brief 停止ModbusTCP服务器 + */ + void StopModbusTCPServer(); + + /** + * @brief 处理ModbusTCP连接状态变化 + * @param connected true-连接,false-断开 + */ + void OnModbusTCPConnectionChanged(bool connected); + + /** + * @brief 处理线圈写入请求(工作信号) + * @param unitId 单元ID + * @param startAddress 起始地址 + * @param quantity 数量 + * @param values 值数组 + * @return 错误码 + */ + IYModbusTCPServer::ErrorCode OnWriteCoils(uint8_t unitId, uint16_t startAddress, + uint16_t quantity, const uint8_t* values); + + /** + * @brief 处理保持寄存器写入请求 + * @param unitId 单元ID + * @param startAddress 起始地址 + * @param quantity 数量 + * @param values 值数组 + * @return 错误码 + */ + IYModbusTCPServer::ErrorCode OnWriteRegisters(uint8_t unitId, uint16_t startAddress, + uint16_t quantity, const uint16_t* values); + + +private: + // ModbusTCP相关 + IYModbusTCPServer* m_pModbusServer; // ModbusTCP服务器实例 + bool m_bServerRunning; // 服务器运行状态 + uint16_t m_nPort; // TCP端口 + + // 机械臂数据 + RobotStatus m_robotStatus; // 机械臂状态 + + // 回调函数 + ConnectionCallback m_connectionCallback; // 连接状态回调 + WorkSignalCallback m_workSignalCallback; // 工作信号回调 + + // Modbus地址映射 + static const uint16_t WORK_SIGNAL_ADDR = 0; // 工作信号线圈地址 (所有相机) + static const uint16_t STATUS_ADDR = 1; // 状态地址 + static const uint16_t COORD_ONE_START_ADDR = 4; // 相机一的检测开始地址 + static const uint16_t COORD_TWO_START_ADDR = 46; // 相机二的检测开始地址 +}; + +#endif // ROBOTPROTOCOL_H diff --git a/App/LapWeld/LapWeldApp/Presenter/Inc/SerialProtocol.h b/App/LapWeld/LapWeldApp/Presenter/Inc/SerialProtocol.h new file mode 100644 index 0000000..766c176 --- /dev/null +++ b/App/LapWeld/LapWeldApp/Presenter/Inc/SerialProtocol.h @@ -0,0 +1,165 @@ +#ifndef SERIALPROTOCOL_H +#define SERIALPROTOCOL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ProtocolCommon.h" + +#include +#include + +/** + * @brief 串口协议封装类 + * 提供串口通信、协议解析、机械臂坐标发送等功能 + */ +class SerialProtocol : public QObject +{ + Q_OBJECT + +public: + /** + * @brief 串口连接状态枚举(使用公共状态定义) + */ + using SerialStatus = ConnectionStatus; + +public: + SerialProtocol(QObject* parent = nullptr); + ~SerialProtocol(); + + /** + * @brief 打开串口连接 + * @param portName 串口名称(如COM1, /dev/ttyUSB0等) + * @param baudRate 波特率 + * @param dataBits 数据位 (5, 6, 7, 8) + * @param stopBits 停止位 (1, 2) + * @param parity 校验位 (0-无校验, 1-奇校验, 2-偶校验) + * @param flowControl 流控制 (0-无, 1-硬件, 2-软件) + * @return 0-成功,其他-错误码 + */ + int OpenSerial(const std::string& portName, int baudRate = 9600, + int dataBits = 8, int stopBits = 1, int parity = 0, int flowControl = 0); + + /** + * @brief 关闭串口连接 + */ + void CloseSerial(); + + /** + * @brief 发送多目标检测结果数据 + * 协议格式:$,DATA,NUM,posX,posY,posZ,posC,posX,posY,posZ,posC...,# + * @param multiTargetData 多目标数据结构 + * @param cameraId 相机ID(从1开始编号) + * @return 0-成功,其他-错误码 + */ + int SendMultiTargetData(const MultiTargetData& multiTargetData, uint16_t cameraId); + + /** + * @brief 获取当前串口连接状态 + * @return 当前状态 + */ + SerialStatus GetSerialStatus() const; + + /** + * @brief 设置连接状态回调 + * @param callback 回调函数 + */ + void SetConnectionCallback(const ConnectionCallback& callback); + + /** + * @brief 设置工作信号回调 + * @param callback 回调函数 + */ + void SetWorkSignalCallback(const WorkSignalCallback& callback); + + /** + * @brief 获取串口是否已打开 + * @return true-已打开,false-未打开 + */ + bool IsOpen() const; + +private slots: + /** + * @brief 串口数据接收槽函数 + */ + void OnSerialDataReceived(); + + /** + * @brief 串口错误处理槽函数 + * @param error 错误类型 + */ + void OnSerialError(QSerialPort::SerialPortError error); + +private: + /** + * @brief 解析接收到的协议数据 + * @param data 接收到的数据 + */ + void ParseProtocolData(const QString& data); + + /** + * @brief 发送数据到串口 + * @param data 要发送的数据 + * @return 0-成功,其他-错误码 + */ + int SendData(const std::string& data); + + /** + * @brief 构造数据协议字符串 + * @param multiTargetData 多目标数据 + * @param cameraId 相机ID + * @return 协议字符串 + */ + 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; // 工作信号回调 + + // 协议常量 + static const QString PROTOCOL_START; // 协议开始标识 "$" + static const QString PROTOCOL_END; // 协议结束标识 "#" + static const QString CMD_START; // 开始命令 "START" + static const QString CMD_DATA; // 数据命令 "DATA" + static const QString SEPARATOR; // 分隔符 "," +}; + +#endif // SERIALPROTOCOL_H \ No newline at end of file diff --git a/App/LapWeld/LapWeldApp/Presenter/Src/ConfigManager.cpp b/App/LapWeld/LapWeldApp/Presenter/Src/ConfigManager.cpp new file mode 100644 index 0000000..9ac945a --- /dev/null +++ b/App/LapWeld/LapWeldApp/Presenter/Src/ConfigManager.cpp @@ -0,0 +1,773 @@ +#include "ConfigManager.h" +#include "VrError.h" +#include "VrLog.h" +#include "PathManager.h" +#include +#include +#include +#include +#include + +// SystemConfig 实现 +CameraUIParam* SystemConfig::GetCameraUIParam(int cameraIndex) +{ + for (auto& param : cameraUIParams) { + if (param.cameraIndex == cameraIndex) { + return ¶m; + } + } + return nullptr; +} + +const CameraUIParam* SystemConfig::GetCameraUIParam(int cameraIndex) const +{ + for (const auto& param : cameraUIParams) { + if (param.cameraIndex == cameraIndex) { + return ¶m; + } + } + return nullptr; +} + +void SystemConfig::SetCameraUIParam(const CameraUIParam& param) +{ + for (auto& existingParam : cameraUIParams) { + if (existingParam.cameraIndex == param.cameraIndex) { + existingParam = param; + return; + } + } + // 如果不存在,则添加新的 + cameraUIParams.push_back(param); +} + +void SystemConfig::EnsureCameraUIParam(int cameraIndex) +{ + for (const auto& param : cameraUIParams) { + if (param.cameraIndex == cameraIndex) { + return; // 已存在 + } + } + + // 不存在,创建默认参数 + CameraUIParam defaultParam; + defaultParam.cameraIndex = cameraIndex; + cameraUIParams.push_back(defaultParam); +} + +// ConfigManager 实现 +ConfigManager::ConfigManager() + : m_pVrConfig(nullptr) + , m_pConfigCmdShareMem(nullptr) + , m_bSharedMemMonitorRunning(false) +{ +} + +ConfigManager::~ConfigManager() +{ + Shutdown(); +} + +bool ConfigManager::Initialize(const std::string& configFilePath) +{ + LOG_INFO("ConfigManager initializing...\n"); + + // 保存配置文件路径 + if (configFilePath.empty()) { + m_configFilePath = PathManager::GetConfigFilePath().toStdString(); + } else { + m_configFilePath = configFilePath; + } + + // 创建VrConfig实例 + if (!IVrConfig::CreateInstance(&m_pVrConfig) || !m_pVrConfig) { + LOG_ERROR("Failed to create VrConfig instance\n"); + return false; + } + + // 加载配置文件 + if (!LoadConfigFromFile(m_configFilePath)) { + LOG_WARNING("Failed to load config file, using default config\n"); + _InitializeDefaultConfig(); + } + + // 启动共享内存监听 + if (!StartSharedMemoryMonitor()) { + LOG_ERROR("Failed to start shared memory monitor\n"); + return false; + } + + LOG_INFO("ConfigManager initialized successfully\n"); + return true; +} + +void ConfigManager::Shutdown() +{ + LOG_INFO("ConfigManager shutting down...\n"); + + // 停止共享内存监听 + StopSharedMemoryMonitor(); + + // 清理监听器 + { + std::lock_guard lock(m_listenersMutex); + m_listeners.clear(); + } + + // 释放VrConfig实例 + if (m_pVrConfig) { + delete m_pVrConfig; + m_pVrConfig = nullptr; + } + + LOG_INFO("ConfigManager shutdown completed\n"); +} + +SystemConfig ConfigManager::GetConfig() const +{ + std::lock_guard lock(m_configMutex); + return m_systemConfig; +} + +CameraUIParam ConfigManager::GetCameraUIParam(int cameraIndex) const +{ + std::lock_guard lock(m_configMutex); + const CameraUIParam* param = m_systemConfig.GetCameraUIParam(cameraIndex); + if (param) { + return *param; + } + + // 返回默认参数 + CameraUIParam defaultParam; + defaultParam.cameraIndex = cameraIndex; + return defaultParam; +} + +VrAlgorithmParams ConfigManager::GetAlgorithmParams() const +{ + std::lock_guard lock(m_configMutex); + return m_systemConfig.configResult.algorithmParams; +} + +ConfigResult ConfigManager::GetConfigResult() const +{ + std::lock_guard lock(m_configMutex); + return m_systemConfig.configResult; +} + +bool ConfigManager::UpdateCameraUIParam(int cameraIndex, const CameraUIParam& param) +{ + bool changed = false; + + { + std::lock_guard lock(m_configMutex); + + CameraUIParam* existingParam = m_systemConfig.GetCameraUIParam(cameraIndex); + if (!existingParam || memcmp(existingParam, ¶m, sizeof(CameraUIParam)) != 0) { + m_systemConfig.SetCameraUIParam(param); + changed = true; + } + } + + if (changed) { + _NotifyCameraParamChanged(cameraIndex); + LOG_INFO("Camera %d UI parameters updated\n", cameraIndex); + } + + return true; +} + +bool ConfigManager::UpdateAlgorithmParams(const VrAlgorithmParams& params) +{ + bool changed = false; + + { + std::lock_guard lock(m_configMutex); + + if (memcmp(&m_systemConfig.configResult.algorithmParams, ¶ms, sizeof(VrAlgorithmParams)) != 0) { + m_systemConfig.configResult.algorithmParams = params; + changed = true; + } + } + + if (changed) { + _NotifyAlgorithmParamChanged(); + LOG_INFO("Algorithm parameters updated\n"); + } + + return true; +} + +bool ConfigManager::UpdateFullConfig(const SystemConfig& config) +{ + { + std::lock_guard lock(m_configMutex); + m_systemConfig = config; + } + + _NotifyConfigChanged(); + LOG_INFO("Full configuration updated\n"); + + return true; +} + +bool ConfigManager::LoadConfigFromFile(const std::string& filePath) +{ + if (!m_pVrConfig) { + LOG_ERROR("VrConfig instance not available\n"); + return false; + } + + try { + ConfigResult configResult = m_pVrConfig->LoadConfig(filePath); + + std::lock_guard lock(m_configMutex); + m_systemConfig.configResult = configResult; + + // 初始化默认的相机UI参数 + m_systemConfig.cameraUIParams.clear(); + for (size_t i = 0; i < configResult.cameraList.size(); ++i) { + CameraUIParam cameraParam; + cameraParam.cameraIndex = static_cast(i + 1); + m_systemConfig.cameraUIParams.push_back(cameraParam); + } + + // 如果没有相机配置,至少添加一个默认相机 + if (m_systemConfig.cameraUIParams.empty()) { + CameraUIParam defaultCamera; + defaultCamera.cameraIndex = 1; + m_systemConfig.cameraUIParams.push_back(defaultCamera); + } + + LOG_INFO("Configuration loaded from file: %s\n", filePath.c_str()); + return true; + + } catch (const std::exception& e) { + LOG_ERROR("Failed to load configuration from file %s: %s\n", filePath.c_str(), e.what()); + return false; + } +} + +bool ConfigManager::SaveConfigToFile(const std::string& filePath) +{ + if (!m_pVrConfig) { + LOG_ERROR("VrConfig instance not available\n"); + return false; + } + + try { + std::lock_guard lock(m_configMutex); + ConfigResult configResult = m_systemConfig.configResult; + lock.~lock_guard(); + + bool result = m_pVrConfig->SaveConfig(filePath, configResult); + if (result) { + LOG_INFO("Configuration saved to file: %s\n", filePath.c_str()); + } else { + LOG_ERROR("Failed to save configuration to file: %s\n", filePath.c_str()); + } + + return result; + + } catch (const std::exception& e) { + LOG_ERROR("Exception while saving configuration to file %s: %s\n", filePath.c_str(), e.what()); + return false; + } +} + +void ConfigManager::AddConfigChangeListener(std::shared_ptr listener) +{ + std::lock_guard lock(m_listenersMutex); + m_listeners.push_back(listener); + + LOG_DEBUG("Config change listener added, total listeners: %zu\n", m_listeners.size()); +} + +void ConfigManager::RemoveConfigChangeListener(std::shared_ptr listener) +{ + std::lock_guard lock(m_listenersMutex); + + auto it = std::find_if(m_listeners.begin(), m_listeners.end(), + [&listener](const std::weak_ptr& weak_listener) { + return weak_listener.lock() == listener; + }); + + if (it != m_listeners.end()) { + m_listeners.erase(it); + LOG_DEBUG("Config change listener removed, remaining listeners: %zu\n", m_listeners.size()); + } +} + +bool ConfigManager::StartSharedMemoryMonitor() +{ + if (m_bSharedMemMonitorRunning) { + LOG_WARNING("Shared memory monitor is already running\n"); + return true; + } + + if (!_InitializeSharedMemory()) { + LOG_ERROR("Failed to initialize shared memory for config monitor\n"); + return false; + } + + // 启动监听线程 + m_bSharedMemMonitorRunning = true; + m_sharedMemMonitorThread = std::thread(&ConfigManager::_SharedMemoryMonitorThread, this); + + LOG_INFO("Shared memory monitor started\n"); + return true; +} + +void ConfigManager::StopSharedMemoryMonitor() +{ + if (!m_bSharedMemMonitorRunning) { + return; + } + + m_bSharedMemMonitorRunning = false; + + // 等待线程结束 + if (m_sharedMemMonitorThread.joinable()) { + m_sharedMemMonitorThread.join(); + } + + // 清理共享内存 + _CleanupSharedMemory(); + + LOG_INFO("Shared memory monitor stopped\n"); +} + +void ConfigManager::_SharedMemoryMonitorThread() +{ + LOG_INFO("Shared memory monitor thread started\n"); + + ConfigCmdSharedData lastData; + memset(&lastData, 0, sizeof(ConfigCmdSharedData)); + + while (m_bSharedMemMonitorRunning) { + try { + // 锁定共享内存并读取数据 + int ret = m_pConfigCmdShareMem->Lock(1000); // 1秒超时 + if (ret != SUCCESS) { + if (ret != SHAREMEM_ERR_TIMEOUT) { + LOG_ERROR("Failed to lock shared memory for config monitor, error: %d\n", ret); + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + continue; + } + + ConfigCmdSharedData currentData; + ret = m_pConfigCmdShareMem->ReadData(0, ¤tData, sizeof(ConfigCmdSharedData)); + m_pConfigCmdShareMem->Unlock(); + + if (ret < 0) { + LOG_ERROR("Failed to read shared memory for config monitor, error: %d\n", ret); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + continue; + } + + // 检查魔数和版本 + if (strcmp(currentData.header.magic, "VRCFG001") != 0 || currentData.header.version != 1) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + continue; + } + + // 检查是否有新数据 + if (!currentData.header.hasNewData) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + continue; + } + + // 验证校验和 + if (!currentData.ValidateChecksum()) { + LOG_ERROR("Config command data checksum validation failed\n"); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + continue; + } + + // 检查是否是新的命令(与上次不同) + if (memcmp(&lastData.data, ¤tData.data, sizeof(ConfigCmdData)) == 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + continue; + } + + // 应用配置命令 + LOG_INFO("Received new config command, type: %d, timestamp: %s\n", + currentData.data.cmdType, currentData.data.timestamp); + + if (_ApplyConfigCommand(currentData.data)) { + LOG_INFO("Config command applied successfully\n"); + } else { + LOG_ERROR("Failed to apply config command\n"); + } + + // 更新最后处理的数据 + lastData = currentData; + + // 标记数据已处理(清除新数据标志) + ret = m_pConfigCmdShareMem->Lock(1000); + if (ret == SUCCESS) { + ConfigCmdSharedData clearData = currentData; + clearData.header.hasNewData = false; + m_pConfigCmdShareMem->WriteData(0, &clearData, sizeof(ConfigCmdSharedData)); + m_pConfigCmdShareMem->Unlock(); + } + + } catch (const std::exception& e) { + LOG_ERROR("Exception in config monitor thread: %s\n", e.what()); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + LOG_INFO("Shared memory monitor thread stopped\n"); +} + +bool ConfigManager::_ApplyConfigCommand(const ConfigCmdData& configData) +{ + switch (configData.cmdType) { + case CONFIG_CMD_CAMERA_EXPOSE: + return _ApplyCameraExpose(configData.cameraParam); + + case CONFIG_CMD_CAMERA_GAIN: + return _ApplyCameraGain(configData.cameraParam); + + case CONFIG_CMD_CAMERA_FRAMERATE: + return _ApplyCameraFrameRate(configData.cameraParam); + + case CONFIG_CMD_CAMERA_SWING: + return _ApplyCameraSwing(configData.swingParam); + + case CONFIG_CMD_ALGO_PARAM: + return _ApplyAlgoParam(configData.algoParam); + + case CONFIG_CMD_CALIB_PARAM: + return _ApplyCalibParam(configData.calibParam); + + case CONFIG_CMD_FULL_CONFIG: + return _ApplyFullConfig(configData.fullConfigParam); + + default: + LOG_ERROR("Unknown config command type: %d\n", configData.cmdType); + return false; + } +} + +bool ConfigManager::_ApplyCameraExpose(const CameraConfigParam& param) +{ + LOG_INFO("Applying camera expose setting: camera %d, expose time: %.2f\n", + param.cameraIndex, param.exposeTime); + + std::lock_guard lock(m_configMutex); + + if (param.cameraIndex == -1) { + // 应用到所有相机 + bool changed = false; + for (auto& cameraParam : m_systemConfig.cameraUIParams) { + if (cameraParam.exposeTime != param.exposeTime) { + cameraParam.exposeTime = param.exposeTime; + changed = true; + } + } + if (changed) { + lock.~lock_guard(); + _NotifyConfigChanged(); + } + } else { + // 应用到指定相机 + m_systemConfig.EnsureCameraUIParam(param.cameraIndex); + CameraUIParam* cameraParam = m_systemConfig.GetCameraUIParam(param.cameraIndex); + if (cameraParam && cameraParam->exposeTime != param.exposeTime) { + cameraParam->exposeTime = param.exposeTime; + lock.~lock_guard(); + _NotifyCameraParamChanged(param.cameraIndex); + } + } + + return true; +} + +bool ConfigManager::_ApplyCameraGain(const CameraConfigParam& param) +{ + LOG_INFO("Applying camera gain setting: camera %d, gain: %.2f\n", + param.cameraIndex, param.gain); + + std::lock_guard lock(m_configMutex); + + if (param.cameraIndex == -1) { + // 应用到所有相机 + bool changed = false; + for (auto& cameraParam : m_systemConfig.cameraUIParams) { + if (cameraParam.gain != param.gain) { + cameraParam.gain = param.gain; + changed = true; + } + } + if (changed) { + lock.~lock_guard(); + _NotifyConfigChanged(); + } + } else { + // 应用到指定相机 + m_systemConfig.EnsureCameraUIParam(param.cameraIndex); + CameraUIParam* cameraParam = m_systemConfig.GetCameraUIParam(param.cameraIndex); + if (cameraParam && cameraParam->gain != param.gain) { + cameraParam->gain = param.gain; + lock.~lock_guard(); + _NotifyCameraParamChanged(param.cameraIndex); + } + } + + return true; +} + +bool ConfigManager::_ApplyCameraFrameRate(const CameraConfigParam& param) +{ + LOG_INFO("Applying camera frame rate setting: camera %d, frame rate: %.2f\n", + param.cameraIndex, param.frameRate); + + std::lock_guard lock(m_configMutex); + + if (param.cameraIndex == -1) { + // 应用到所有相机 + bool changed = false; + for (auto& cameraParam : m_systemConfig.cameraUIParams) { + if (cameraParam.frameRate != param.frameRate) { + cameraParam.frameRate = param.frameRate; + changed = true; + } + } + if (changed) { + lock.~lock_guard(); + _NotifyConfigChanged(); + } + } else { + // 应用到指定相机 + m_systemConfig.EnsureCameraUIParam(param.cameraIndex); + CameraUIParam* cameraParam = m_systemConfig.GetCameraUIParam(param.cameraIndex); + if (cameraParam && cameraParam->frameRate != param.frameRate) { + cameraParam->frameRate = param.frameRate; + lock.~lock_guard(); + _NotifyCameraParamChanged(param.cameraIndex); + } + } + + return true; +} + +bool ConfigManager::_ApplyCameraSwing(const SwingConfigParam& param) +{ + LOG_INFO("Applying camera swing setting: camera %d, speed: %.2f, angles: %.2f-%.2f\n", + param.cameraIndex, param.swingSpeed, param.startAngle, param.stopAngle); + + std::lock_guard lock(m_configMutex); + + if (param.cameraIndex == -1) { + // 应用到所有相机 + bool changed = false; + for (auto& cameraParam : m_systemConfig.cameraUIParams) { + if (cameraParam.swingSpeed != param.swingSpeed || + cameraParam.swingStartAngle != param.startAngle || + cameraParam.swingStopAngle != param.stopAngle) { + cameraParam.swingSpeed = param.swingSpeed; + cameraParam.swingStartAngle = param.startAngle; + cameraParam.swingStopAngle = param.stopAngle; + changed = true; + } + } + if (changed) { + lock.~lock_guard(); + _NotifyConfigChanged(); + } + } else { + // 应用到指定相机 + m_systemConfig.EnsureCameraUIParam(param.cameraIndex); + CameraUIParam* cameraParam = m_systemConfig.GetCameraUIParam(param.cameraIndex); + if (cameraParam && (cameraParam->swingSpeed != param.swingSpeed || + cameraParam->swingStartAngle != param.startAngle || + cameraParam->swingStopAngle != param.stopAngle)) { + cameraParam->swingSpeed = param.swingSpeed; + cameraParam->swingStartAngle = param.startAngle; + cameraParam->swingStopAngle = param.stopAngle; + lock.~lock_guard(); + _NotifyCameraParamChanged(param.cameraIndex); + } + } + + return true; +} + +bool ConfigManager::_ApplyAlgoParam(const AlgoConfigParam& param) +{ + LOG_INFO("Applying algorithm parameter: %s = %.3f\n", param.paramName, param.paramValue); + + // 算法参数设置暂时只记录日志,具体实现可以根据需要扩展 + return true; +} + +bool ConfigManager::_ApplyCalibParam(const CalibConfigParam& param) +{ + LOG_INFO("Applying calibration parameter for camera %d\n", param.cameraIndex); + + // 标定参数设置暂时只记录日志,具体实现可以根据需要扩展 + return true; +} + +bool ConfigManager::_ApplyFullConfig(const FullConfigParam& param) +{ + LOG_INFO("Applying full configuration update\n"); + + // JSON反序列化和应用完整配置 + SystemConfig newConfig; + if (_DeserializeConfigFromJson(param.configJson, newConfig)) { + UpdateFullConfig(newConfig); + return true; + } + + LOG_ERROR("Failed to deserialize full configuration from JSON\n"); + return false; +} + +void ConfigManager::_NotifyConfigChanged() +{ + SystemConfig currentConfig = GetConfig(); + + std::lock_guard lock(m_listenersMutex); + _CleanupExpiredListeners(); + + for (auto& weakListener : m_listeners) { + if (auto listener = weakListener.lock()) { + try { + listener->OnSystemConfigChanged(currentConfig); + } catch (const std::exception& e) { + LOG_ERROR("Exception in config change listener: %s\n", e.what()); + } + } + } +} + +void ConfigManager::_NotifyCameraParamChanged(int cameraIndex) +{ + CameraUIParam cameraParam = GetCameraUIParam(cameraIndex); + + std::lock_guard lock(m_listenersMutex); + _CleanupExpiredListeners(); + + for (auto& weakListener : m_listeners) { + if (auto listener = weakListener.lock()) { + try { + listener->OnCameraParamChanged(cameraIndex, cameraParam); + } catch (const std::exception& e) { + LOG_ERROR("Exception in camera param change listener: %s\n", e.what()); + } + } + } +} + +void ConfigManager::_NotifyAlgorithmParamChanged() +{ + VrAlgorithmParams algorithmParams = GetAlgorithmParams(); + + std::lock_guard lock(m_listenersMutex); + _CleanupExpiredListeners(); + + for (auto& weakListener : m_listeners) { + if (auto listener = weakListener.lock()) { + try { + listener->OnAlgorithmParamChanged(algorithmParams); + } catch (const std::exception& e) { + LOG_ERROR("Exception in algorithm param change listener: %s\n", e.what()); + } + } + } +} + +void ConfigManager::_CleanupExpiredListeners() +{ + // 移除已过期的weak_ptr + m_listeners.erase( + std::remove_if(m_listeners.begin(), m_listeners.end(), + [](const std::weak_ptr& weak_listener) { + return weak_listener.expired(); + }), + m_listeners.end()); +} + +bool ConfigManager::_InitializeSharedMemory() +{ + if (m_pConfigCmdShareMem) { + return true; // 已经初始化 + } + + m_pConfigCmdShareMem = CreateShareMemInstance(); + if (!m_pConfigCmdShareMem) { + return false; + } + + // 尝试打开已存在的共享内存,如果不存在则创建 + int ret = m_pConfigCmdShareMem->CreateOrOpen(CONFIG_CMD_SHARED_MEM_NAME, CONFIG_CMD_SHARED_MEM_SIZE, false); + if (ret != SUCCESS) { + // 如果打开失败,尝试创建新的 + ret = m_pConfigCmdShareMem->CreateOrOpen(CONFIG_CMD_SHARED_MEM_NAME, CONFIG_CMD_SHARED_MEM_SIZE, true); + if (ret != SUCCESS) { + _CleanupSharedMemory(); + return false; + } + } + + // 映射内存 + void* mappedAddr = m_pConfigCmdShareMem->MapView(); + if (!mappedAddr) { + _CleanupSharedMemory(); + return false; + } + + return true; +} + +void ConfigManager::_CleanupSharedMemory() +{ + if (m_pConfigCmdShareMem) { + m_pConfigCmdShareMem->UnmapView(); + m_pConfigCmdShareMem->Close(); + DestroyShareMemInstance(m_pConfigCmdShareMem); + m_pConfigCmdShareMem = nullptr; + } +} + +bool ConfigManager::_InitializeDefaultConfig() +{ + std::lock_guard lock(m_configMutex); + + // 初始化默认ConfigResult + m_systemConfig.configResult = ConfigResult(); + + // 添加默认相机配置 + DeviceInfo defaultCamera; + defaultCamera.name = "Camera1"; + defaultCamera.ip = "192.168.1.100"; + m_systemConfig.configResult.cameraList.push_back(defaultCamera); + + // 初始化默认相机UI参数 + m_systemConfig.cameraUIParams.clear(); + CameraUIParam defaultCameraUI; + defaultCameraUI.cameraIndex = 1; + m_systemConfig.cameraUIParams.push_back(defaultCameraUI); + + LOG_INFO("Default configuration initialized\n"); + return true; +} + +std::string ConfigManager::_SerializeConfigToJson(const SystemConfig& config) +{ + // 简化的JSON序列化实现 + // 在实际项目中,建议使用完整的JSON库 + return "{}"; // 暂时返回空JSON +} + +bool ConfigManager::_DeserializeConfigFromJson(const std::string& json, SystemConfig& config) +{ + // 简化的JSON反序列化实现 + // 在实际项目中,建议使用完整的JSON库 + return false; // 暂时返回失败 +} \ No newline at end of file diff --git a/App/LapWeld/LapWeldApp/Presenter/Src/DetectPresenter.cpp b/App/LapWeld/LapWeldApp/Presenter/Src/DetectPresenter.cpp new file mode 100644 index 0000000..372ccfa --- /dev/null +++ b/App/LapWeld/LapWeldApp/Presenter/Src/DetectPresenter.cpp @@ -0,0 +1,185 @@ +#include "DetectPresenter.h" +#include "SX_lapWeldDetection_Export.h" + + +DetectPresenter::DetectPresenter(/* args */) +{ +} + +DetectPresenter::~DetectPresenter() +{ +} + +int DetectPresenter::DetectLapWeld(std::vector& detectionDataCache, + const SSX_lapWeldParam& algoParam, + const SSG_planeCalibPara& cameraCalibParam, + const VrDebugParam& debugParam, + LaserDataLoader& dataLoader, + const double clibMatrix[16], + DetectionResult& detectionResult) +{ + if (detectionDataCache.empty()) { + LOG_WARNING("No cached detection data available\n"); + return ERR_CODE(DEV_DATA_INVALID); + } + + // 2. 数据预处理:调平和去除地面(使用当前相机的调平参数) + for (size_t i = 0; i < detectionDataCache.size(); i++) { + // 将SVzNL3DLaserLine转换为std::vector + std::vector lineData(detectionDataCache[i].nPositionCnt); + for (int j = 0; j < detectionDataCache[i].nPositionCnt; j++) { + lineData[j] = detectionDataCache[i].p3DPosition[j]; + } + sx_lineDataR(lineData, cameraCalibParam.planeCalib, cameraCalibParam.planeHeight); + + // 将处理后的数据复制回原结构 + for (int j = 0; j < detectionDataCache[i].nPositionCnt; j++) { + detectionDataCache[i].p3DPosition[j] = lineData[j]; + } + } + + // 3. 调用搭接焊缝检测算法接口(使用当前相机的调平参数) + std::vector> objOps; + std::vector> scanLines(detectionDataCache.size()); + for (size_t i = 0; i < detectionDataCache.size(); i++) { + scanLines[i].resize(detectionDataCache[i].nPositionCnt); + for (int j = 0; j < detectionDataCache[i].nPositionCnt; j++) { + scanLines[i][j].pt3D.x = detectionDataCache[i].p3DPosition[j].pt3D.x; + scanLines[i][j].pt3D.y = detectionDataCache[i].p3DPosition[j].pt3D.y; + scanLines[i][j].pt3D.z = detectionDataCache[i].p3DPosition[j].pt3D.z; + } + } + + // 定义搭接焊缝检测需要的参数 + SSG_cornerParam cornerParam; + SSG_treeGrowParam growParam; + int errCode = 0; + sx_getLapWeldPostion(scanLines, cornerParam, growParam, algoParam, objOps, &errCode); + + if (errCode != 0) { + LOG_ERROR("sx_getLapWeldPostion failed, error code: %d\n", errCode); + return errCode; + } + + // 从点云数据生成投影图像 + // 注意:由于数据结构变化,需要调整图像生成函数 + detectionResult.image = QImage(800, 600, QImage::Format_RGB32); + detectionResult.image.fill(Qt::black); + + // 转换检测结果为UI显示格式(使用机械臂坐标系数据) + for (size_t i = 0; i < objOps.size(); i++) { + if (!objOps[i].empty()) { + const SVzNL3DPoint& obj = objOps[i][0]; // 使用第一个点作为参考 + + // 进行坐标转换:从算法坐标系转换到机械臂坐标系 + SVzNL3DPoint targetObj; + targetObj.x = obj.x; + targetObj.y = obj.y; + targetObj.z = obj.z; + + SVzNL3DPoint robotObj; + CVrConvert::EyeToRobot(targetObj, robotObj, clibMatrix); + + // 创建位置数据(使用转换后的机械臂坐标) + LapWeldPosition pos; + pos.x = robotObj.x; // 机械臂坐标X + pos.y = robotObj.y; // 机械臂坐标Y + pos.z = robotObj.z; // 机械臂坐标Z + pos.roll = 0.0; // 搭接焊缝检测不提供姿态角 + pos.pitch = 0.0; // 搭接焊缝检测不提供姿态角 + pos.yaw = 0.0; // 搭接焊缝检测不提供姿态角 + + detectionResult.positions.push_back(pos); + + if(debugParam.enableDebug && debugParam.printDetailLog){ + LOG_INFO("[Algo Thread] Object %zu Eye Coords: X=%.2f, Y=%.2f, Z=%.2f\n", + i, obj.x, obj.y, obj.z); + LOG_INFO("[Algo Thread] Object %zu Robot Coords: X=%.2f, Y=%.2f, Z=%.2f, Roll=%.2f, Pitch=%.2f, Yaw=%.2f\n", + i, pos.x, pos.y, pos.z, pos.roll, pos.pitch, pos.yaw); + } + } + } + + return 0; +} + +// 新增:处理统一数据格式和项目类型的DetectLapWeld函数 +int DetectPresenter::DetectLapWeld( + int cameraIndex, + std::vector>& laserLines, + ProjectType projectType, + const SSX_lapWeldParam& algoParam, + const SSG_planeCalibPara& cameraCalibParam, + const VrDebugParam& debugParam, + LaserDataLoader& dataLoader, + const double clibMatrix[16], + DetectionResult& detectionResult) +{ + if (laserLines.empty()) { + LOG_WARNING("No laser lines data available\n"); + return ERR_CODE(DEV_DATA_INVALID); + } + + // 保存debug数据 + std::string timeStamp = CVrDateUtils::GetNowTime(); + if(debugParam.enableDebug && debugParam.savePointCloud){ + LOG_INFO("[Algo Thread] Debug mode is enabled, saving point cloud data\n"); + + // 获取当前时间戳,格式为YYYYMMDDHHMMSS + std::string fileName = debugParam.debugOutputPath + "/Laserline_" + std::to_string(cameraIndex) + "_" + timeStamp + ".txt"; + + // 直接使用统一格式保存数据 + dataLoader.SaveLaserScanData(fileName, laserLines, laserLines.size(), 0.0, 0, 0); + } + + + if(debugParam.enableDebug && debugParam.printDetailLog) + { + LOG_INFO("[Algo Thread] clibMatrix: \n\t[%.3f, %.3f, %.3f, %.3f] \n\t[ %.3f, %.3f, %.3f, %.3f] \n\t[ %.3f, %.3f, %.3f, %.3f] \n\t[ %.3f, %.3f, %.3f, %.3f]\n", + clibMatrix[0], clibMatrix[1], clibMatrix[2], clibMatrix[3], + clibMatrix[4], clibMatrix[5], clibMatrix[6], clibMatrix[7], + clibMatrix[8], clibMatrix[9], clibMatrix[10], clibMatrix[11], + clibMatrix[12], clibMatrix[13], clibMatrix[14], clibMatrix[15]); + + // 1. 使用成员变量算法参数(已在初始化时从XML读取) + LOG_INFO("[Algo Thread] Using algorithm parameters from XML configuration\n"); + LOG_INFO(" Lap Weld: lapHeight=%.1f, weldMinLen=%.1f, weldRefPoints=%d\n",algoParam.lapHeight, algoParam.weldMinLen, algoParam.weldRefPoints); + + LOG_INFO(" Plane height: %.3f\n", cameraCalibParam.planeHeight); + LOG_INFO(" Plane calibration matrix: [%.3f, %.3f, %.3f; %.3f, %.3f, %.3f; %.3f, %.3f, %.3f]\n", + cameraCalibParam.planeCalib[0], cameraCalibParam.planeCalib[1], cameraCalibParam.planeCalib[2], + cameraCalibParam.planeCalib[3], cameraCalibParam.planeCalib[4], cameraCalibParam.planeCalib[5], + cameraCalibParam.planeCalib[6], cameraCalibParam.planeCalib[7], cameraCalibParam.planeCalib[8]); + } + + int nRet = SUCCESS; + + // 普通项目:转换为XYZ格式进行算法调用 + std::vector xyzData; + int convertResult = dataLoader.ConvertToSVzNL3DLaserLine(laserLines, xyzData); + if (convertResult == SUCCESS && !xyzData.empty()) { + nRet = DetectLapWeld(xyzData, algoParam, cameraCalibParam, debugParam, dataLoader, clibMatrix, detectionResult); + // 清理XYZ内存 + dataLoader.FreeConvertedData(xyzData); + + } else { + LOG_WARNING("Failed to convert data to XYZ format or no XYZ data available\n"); + return ERR_CODE(DEV_DATA_INVALID); + } + + if(debugParam.enableDebug && debugParam.saveDebugImage){ + // 获取当前时间戳,格式为YYYYMMDDHHMMSS + std::string fileName = debugParam.debugOutputPath + "/Image_" + std::to_string(cameraIndex) + "_" + timeStamp + ".png"; + LOG_INFO("[Algo Thread] Debug image saved image : %s\n", fileName.c_str()); + + // 保存检测结果图片 + if (!detectionResult.image.isNull()) { + QString qFileName = QString::fromStdString(fileName); + detectionResult.image.save(qFileName); + } else { + LOG_WARNING("[Algo Thread] No valid image to save for debug\n"); + } + } + + return nRet; +} diff --git a/App/LapWeld/LapWeldApp/Presenter/Src/LapWeldPresenter.cpp b/App/LapWeld/LapWeldApp/Presenter/Src/LapWeldPresenter.cpp new file mode 100644 index 0000000..796cbb4 --- /dev/null +++ b/App/LapWeld/LapWeldApp/Presenter/Src/LapWeldPresenter.cpp @@ -0,0 +1,1372 @@ +#include "LapWeldPresenter.h" +#include "VrError.h" +#include "VrLog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Version.h" +#include "VrTimeUtils.h" +#include "VrDateUtils.h" +#include "SX_lapWeldDetection_Export.h" +#include "SG_baseDataType.h" +#include "VrConvert.h" +#include "PointCloudImageUtils.h" + +LapWeldPresenter::LapWeldPresenter() + : m_vrConfig(nullptr) + , m_pStatus(nullptr) + , m_pDetectPresenter(nullptr) + , m_pRobotProtocol(nullptr) + , m_bCameraConnected(false) + , m_bRobotConnected(false) + , m_currentWorkStatus(WorkStatus::Error) +{ + m_detectionDataCache.clear(); +} + +LapWeldPresenter::~LapWeldPresenter() +{ + // 停止配置管理器 + if (m_pConfigManager) { + m_pConfigManager->Shutdown(); + m_pConfigManager.reset(); + } + + // 停止算法检测线程 + m_bAlgoDetectThreadRunning = false; + m_algoDetectCondition.notify_all(); + + // 等待算法检测线程结束 + if (m_algoDetectThread.joinable()) { + m_algoDetectThread.join(); + } + + // 释放缓存的检测数据 + _ClearDetectionDataCache(); + + // 释放机械臂协议 + if (m_pRobotProtocol) { + m_pRobotProtocol->Deinitialize(); + delete m_pRobotProtocol; + m_pRobotProtocol = nullptr; + } + + // 释放串口协议 + if (m_pSerialProtocol) { + m_pSerialProtocol->CloseSerial(); + delete m_pSerialProtocol; + m_pSerialProtocol = nullptr; + } + + // 释放相机设备资源 + for(auto it = m_vrEyeDeviceList.begin(); it != m_vrEyeDeviceList.end(); it++) + { + if (it->second != nullptr) { + it->second->CloseDevice(); + delete it->second; + it->second = nullptr; + } + } + m_vrEyeDeviceList.clear(); + + // 释放检测处理器 + if(m_pDetectPresenter) + { + delete m_pDetectPresenter; + m_pDetectPresenter = nullptr; + } + + // 释放配置对象 + if (m_vrConfig) { + delete m_vrConfig; + m_vrConfig = nullptr; + } +} + +int LapWeldPresenter::Init() +{ + LOG_DEBUG("Start APP Version: %s\n", LAPWELD_FULL_VERSION_STRING); + // 初始化连接状态 + m_bCameraConnected = false; + m_currentWorkStatus = WorkStatus::InitIng; + + m_pStatus->OnWorkStatusChanged(m_currentWorkStatus); + + m_pDetectPresenter = new DetectPresenter(); + + // 初始化VrConfig模块 + IVrConfig::CreateInstance(&m_vrConfig); + + // 设置配置改变通知回调 + if (m_vrConfig) { + m_vrConfig->SetConfigChangeNotify(this); + } + + // 获取配置文件路径 + QString configPath = PathManager::GetConfigFilePath(); + + // 读取IP列表 + ConfigResult configResult = m_vrConfig->LoadConfig(configPath.toStdString()); + m_projectType = configResult.projectType; + int nRet = SUCCESS; + + // 初始化算法参数 + nRet = InitAlgorithmParams(); + if (nRet != 0) { + m_pStatus->OnStatusUpdate("算法参数初始化失败"); + LOG_ERROR("Algorithm parameters initialization failed with error: %d\n", nRet); + } else { + m_pStatus->OnStatusUpdate("算法参数初始化成功"); + LOG_INFO("Algorithm parameters initialization successful\n"); + } + + InitCamera(configResult.cameraList); + + LOG_INFO("Camera initialization completed. Connected cameras: %zu, default camera index: %d\n", + m_vrEyeDeviceList.size(), m_currentCameraIndex); + + // 初始化机械臂协议 + nRet = InitRobotProtocol(); + if (nRet != 0) { + m_pStatus->OnStatusUpdate("机械臂服务初始化失败"); + m_bRobotConnected = false; + } else { + m_pStatus->OnStatusUpdate("机械臂服务初始化成功"); + } + + // 初始化串口协议 + nRet = InitSerialProtocol(); + if (nRet != 0) { + m_pStatus->OnStatusUpdate("串口服务初始化失败"); + m_bSerialConnected = false; + } else { + m_pStatus->OnStatusUpdate("串口服务初始化成功"); + m_bSerialConnected = true; + m_pStatus->OnSerialConnectionChanged(true); + } + + m_bAlgoDetectThreadRunning = true; + m_algoDetectThread = std::thread(&LapWeldPresenter::_AlgoDetectThread, this); + m_algoDetectThread.detach(); + + m_pStatus->OnStatusUpdate("设备初始化完成"); + + CheckAndUpdateWorkStatus(); + + QString str = QString("%1 配置初始化成功").arg(ProjectTypeToString(configResult.projectType).c_str()); + m_pStatus->OnStatusUpdate(str.toStdString()); + LOG_INFO("ConfigManager initialized successfully\n"); + + return SUCCESS; +} + +// 相机协议相关方法 +int LapWeldPresenter::InitCamera(std::vector& cameraList) +{ + // 通知UI相机个数 + int cameraCount = cameraList.size(); + + // 初始化相机列表,预分配空间 + m_vrEyeDeviceList.resize(cameraCount, std::make_pair("", nullptr)); + for(int i = 0; i < cameraCount; i++) + { + m_vrEyeDeviceList[i] = std::make_pair(cameraList[i].name, nullptr); + } + m_pStatus->OnCameraCountChanged(cameraCount); + + if(cameraCount > 0){ + if (cameraCount >= 1) { + // 尝试打开相机 + int nRet = _OpenDevice(1, cameraList[0].name.c_str(), cameraList[0].ip.c_str(), m_projectType); + + m_pStatus->OnCamera1StatusChanged(nRet == SUCCESS); + m_bCameraConnected = (nRet == SUCCESS); + } + + if (cameraCount >= 2) { + + // 尝试打开相机 + int nRet = _OpenDevice(2, cameraList[1].name.c_str(), cameraList[1].ip.c_str(), m_projectType); + + m_pStatus->OnCamera2StatusChanged(nRet == SUCCESS); + m_bCameraConnected = (nRet == SUCCESS); + } + } else { + m_vrEyeDeviceList.resize(1, std::make_pair("", nullptr)); + _OpenDevice(1, "相机", nullptr, m_projectType); + } + + // 设置默认相机索引为第一个连接的相机 + m_currentCameraIndex = 1; // 默认从1开始 + for (int i = 0; i < static_cast(m_vrEyeDeviceList.size()); i++) { + if (m_vrEyeDeviceList[i].second != nullptr) { + m_currentCameraIndex = i + 1; // 找到第一个连接的相机 + break; + } + } +return SUCCESS; +} + +// 初始化机械臂协议 +int LapWeldPresenter::InitRobotProtocol() +{ + LOG_DEBUG("Start initializing robot protocol\n"); + m_pStatus->OnStatusUpdate("机械臂服务初始化..."); + + // 创建RobotProtocol实例 + if(nullptr == m_pRobotProtocol){ + m_pRobotProtocol = new RobotProtocol(); + } + + // 初始化协议服务(使用端口502) + int nRet = m_pRobotProtocol->Initialize(5020); + + // 设置连接状态回调 + m_pRobotProtocol->SetConnectionCallback([this](bool connected) { + this->OnRobotConnectionChanged(connected); + }); + + // 设置工作信号回调 + m_pRobotProtocol->SetWorkSignalCallback([this](bool startWork, int cameraIndex) { + return this->OnRobotWorkSignal(startWork, cameraIndex); + }); + + + LOG_INFO("Robot protocol initialization completed successfully\n"); + return nRet; + +} + +// 串口协议相关方法实现 +int LapWeldPresenter::InitSerialProtocol() +{ + if (m_pSerialProtocol) { + LOG_WARNING("Serial protocol already initialized\n"); + return SUCCESS; + } + + LOG_INFO("Initializing serial protocol\n"); + + // 获取串口配置 + QString configPath = PathManager::GetConfigFilePath(); + ConfigResult configResult = m_vrConfig->LoadConfig(configPath.toStdString()); + + // 如果串口未启用,则不初始化 + if (!configResult.serialConfig.enabled) { + LOG_INFO("Serial communication is disabled in configuration\n"); + return ERR_CODE(DEV_ARG_INVAILD); + } + + // 创建串口协议实例 + m_pSerialProtocol = new SerialProtocol(); + + // 设置串口回调函数 + m_pSerialProtocol->SetConnectionCallback([this](bool connected) { + this->OnSerialConnectionChanged(connected); + }); + + m_pSerialProtocol->SetWorkSignalCallback([this](bool startWork, int cameraIndex) { + return this->OnSerialWorkSignal(startWork, cameraIndex); + }); + + // 打开串口 + int result = m_pSerialProtocol->OpenSerial( + configResult.serialConfig.portName, + configResult.serialConfig.baudRate, + configResult.serialConfig.dataBits, + configResult.serialConfig.stopBits, + configResult.serialConfig.parity, + configResult.serialConfig.flowControl + ); + + if (result == SUCCESS) { + LOG_INFO("Serial protocol initialization successful\n"); + m_bSerialConnected = true; + return SUCCESS; + } else { + LOG_ERROR("Serial protocol initialization failed with error: %d\n", result); + m_bSerialConnected = false; + return result; + } +} + +// 初始化算法参数 +int LapWeldPresenter::InitAlgorithmParams() +{ + LOG_DEBUG("Start initializing algorithm parameters\n"); + QString exePath = QCoreApplication::applicationFilePath(); + + // 清空现有的手眼标定矩阵列表 + m_clibMatrixList.clear(); + + // 获取手眼标定文件路径并确保文件存在 + QString clibPath = PathManager::GetCalibrationFilePath(); + + LOG_INFO("Loading hand-eye matrices from: %s\n", clibPath.toStdString().c_str()); + + // 读取存在的矩阵数量 + int nExistMatrixNum = CVrConvert::GetClibMatrixCount(clibPath.toStdString().c_str()); + LOG_INFO("Found %d hand-eye calibration matrices\n", nExistMatrixNum); + + // 循环加载每个矩阵 + for(int matrixIndex = 0; matrixIndex < nExistMatrixNum; matrixIndex++) + { + // 构造矩阵标识符 + char matrixIdent[64]; +#ifdef _WIN32 + sprintf_s(matrixIdent, "CalibMatrixInfo_%d", matrixIndex); +#else + sprintf(matrixIdent, "CalibMatrixInfo_%d", matrixIndex); +#endif + + // 创建新的标定矩阵结构 + CalibMatrix calibMatrix; + + // 初始化为单位矩阵 + double initClibMatrix[16] = { + 1.0, 0.0, 0.0, 0.0, // 第一行 + 0.0, 1.0, 0.0, 0.0, // 第二行 + 0.0, 0.0, 1.0, 0.0, // 第三行 + 0.0, 0.0, 0.0, 1.0 // 第四行 + }; + + // 加载矩阵数据 + bool loadSuccess = CVrConvert::LoadClibMatrix(clibPath.toStdString().c_str(), matrixIdent, "dCalibMatrix", calibMatrix.clibMatrix); + + if(loadSuccess) + { + m_clibMatrixList.push_back(calibMatrix); + LOG_INFO("Successfully loaded matrix %d\n", matrixIndex); + + // 输出矩阵内容 + QString clibMatrixStr; + LOG_INFO("Matrix %d content:\n", matrixIndex); + for (int i = 0; i < 4; ++i) { + clibMatrixStr.clear(); + for (int j = 0; j < 4; ++j) { + clibMatrixStr += QString::asprintf("%8.4f ", calibMatrix.clibMatrix[i * 4 + j]); + } + LOG_INFO(" %s\n", clibMatrixStr.toStdString().c_str()); + } + } + else + { + LOG_WARNING("Failed to load matrix %d, using identity matrix\n", matrixIndex); + // 如果加载失败,使用单位矩阵 + memcpy(calibMatrix.clibMatrix, initClibMatrix, sizeof(initClibMatrix)); + m_clibMatrixList.push_back(calibMatrix); + } + + } + + LOG_INFO("Total loaded %zu hand-eye calibration matrices\n", m_clibMatrixList.size()); + + // 获取配置文件路径 + QString configPath = PathManager::GetConfigFilePath(); + + LOG_INFO("Loading config: %s\n", configPath.toStdString().c_str()); + + // 读取配置文件 + ConfigResult configResult = m_vrConfig->LoadConfig(configPath.toStdString()); + const VrAlgorithmParams& xmlParams = configResult.algorithmParams; + + // 保存调试参数 + m_debugParam = configResult.debugParam; + + LOG_INFO("Loaded XML params - LapWeld: lapHeight=%.1f, weldMinLen=%.1f, weldRefPoints=%d\n", + xmlParams.lapWeldParam.lapHeight, xmlParams.lapWeldParam.weldMinLen, xmlParams.lapWeldParam.weldRefPoints); + LOG_INFO("Loaded XML params - Filter: continuityTh=%.1f, outlierTh=%d\n", + xmlParams.filterParam.continuityTh, xmlParams.filterParam.outlierTh); + + // 初始化算法参数结构 + memset(&m_algoParam, 0, sizeof(SSX_lapWeldParam)); + + // 设置激光焊接参数 + m_algoParam.lapHeight = xmlParams.lapWeldParam.lapHeight; + m_algoParam.weldMinLen = xmlParams.lapWeldParam.weldMinLen; + m_algoParam.weldRefPoints = xmlParams.lapWeldParam.weldRefPoints; + + // 设置平面校准参数(保存所有相机的配置) + m_cameraCalibParams = xmlParams.planeCalibParam.cameraCalibParams; + + LOG_INFO("projectType: %s\n", ProjectTypeToString(m_projectType).c_str()); + + LOG_INFO("Algorithm parameters initialized successfully:\n"); + LOG_INFO(" LapWeld: lapHeight=%.1f, weldMinLen=%.1f, weldRefPoints=%d\n", + m_algoParam.lapHeight, m_algoParam.weldMinLen, m_algoParam.weldRefPoints); + + // 循环打印所有相机的调平参数 + LOG_INFO("Loading plane calibration parameters for all cameras:\n"); + for (const auto& cameraParam : m_cameraCalibParams) { + LOG_INFO("Camera %d (%s) calibration parameters:\n", + cameraParam.cameraIndex, cameraParam.cameraName.c_str()); + LOG_INFO(" Is calibrated: %s\n", cameraParam.isCalibrated ? "YES" : "NO"); + LOG_INFO(" Plane height: %.3f\n", cameraParam.planeHeight); + LOG_INFO(" Plane calibration matrix:\n"); + LOG_INFO(" [%.3f, %.3f, %.3f]\n", cameraParam.planeCalib[0], cameraParam.planeCalib[1], cameraParam.planeCalib[2]); + LOG_INFO(" [%.3f, %.3f, %.3f]\n", cameraParam.planeCalib[3], cameraParam.planeCalib[4], cameraParam.planeCalib[5]); + LOG_INFO(" [%.3f, %.3f, %.3f]\n", cameraParam.planeCalib[6], cameraParam.planeCalib[7], cameraParam.planeCalib[8]); + LOG_INFO(" Inverse rotation matrix:\n"); + LOG_INFO(" [%.3f, %.3f, %.3f]\n", cameraParam.invRMatrix[0], cameraParam.invRMatrix[1], cameraParam.invRMatrix[2]); + LOG_INFO(" [%.3f, %.3f, %.3f]\n", cameraParam.invRMatrix[3], cameraParam.invRMatrix[4], cameraParam.invRMatrix[5]); + LOG_INFO(" [%.3f, %.3f, %.3f]\n", cameraParam.invRMatrix[6], cameraParam.invRMatrix[7], cameraParam.invRMatrix[8]); + LOG_INFO(" --------------------------------\n"); + } + + + return SUCCESS; +} + +// 手眼标定矩阵管理方法实现 +const CalibMatrix LapWeldPresenter::GetClibMatrix(int index) const +{ + CalibMatrix clibMatrix; + + double initClibMatrix[16] = { + 1.0, 0.0, 0.0, 0.0, // 第一行 + 0.0, 1.0, 0.0, 0.0, // 第二行 + 0.0, 0.0, 1.0, 0.0, // 第三行 + 0.0, 0.0, 0.0, 1.0 // 第四行 + }; + memcpy(clibMatrix.clibMatrix, initClibMatrix, sizeof(initClibMatrix)); + + if (index >= 0 && index < static_cast(m_clibMatrixList.size())) { + clibMatrix = m_clibMatrixList[index]; + memcpy(clibMatrix.clibMatrix, m_clibMatrixList[index].clibMatrix, sizeof(initClibMatrix)); + } else { + LOG_WARNING("Invalid hand-eye calibration matrix\n"); + } + + return clibMatrix; +} + +// 机械臂协议回调函数实现 +void LapWeldPresenter::OnRobotConnectionChanged(bool connected) +{ + LOG_INFO("Robot connection status changed: %s\n", (connected ? "connected" : "disconnected")); + + // 更新机械臂连接状态 + m_bRobotConnected = connected; + + if (m_pStatus) { + m_pStatus->OnRobotConnectionChanged(connected); + + // 发送详细的页面状态信息 + if (connected) { + m_pStatus->OnStatusUpdate("机械臂连接成功,通信正常"); + } else { + m_pStatus->OnStatusUpdate("机械臂连接断开,请检查网络连接"); + } + } + + // 检查并更新工作状态 + CheckAndUpdateWorkStatus(); +} + +bool LapWeldPresenter::OnRobotWorkSignal(bool startWork, int cameraIndex) +{ + LOG_INFO("Received robot work signal: %s for camera index: %d\n", (startWork ? "start work" : "stop work"), cameraIndex); + + if(!_SinglePreDetection(cameraIndex)){ + return false; + } + + int nRet = _SingleDetection(cameraIndex, startWork); + + if(nRet != SUCCESS){ + m_currentWorkStatus = WorkStatus::Ready; + if (m_pStatus) { + m_pStatus->OnWorkStatusChanged(WorkStatus::Error); + } + } + return nRet == SUCCESS; +} + +void LapWeldPresenter::SetStatusCallback(IYLapWeldStatus* status) +{ + m_pStatus = status; +} + +// 模拟检测函数,用于演示 +int LapWeldPresenter::StartDetection(int cameraIdx, bool isAuto) +{ + LOG_INFO("--------------------------------\n"); + LOG_INFO("Start detection with camera index: %d\n", cameraIdx); + + // 检查设备状态是否准备就绪 + if (isAuto && m_currentWorkStatus != WorkStatus::Ready) { + LOG_INFO("Device not ready, cannot start detection\n"); + if (m_pStatus) { + m_pStatus->OnStatusUpdate("设备未准备就绪,无法开始检测"); + } + return ERR_CODE(DEV_BUSY); + } + + // 保存当前使用的相机ID(从1开始编号) + if(-1 != cameraIdx){ + m_currentCameraIndex = cameraIdx; + } + int cameraIndex = m_currentCameraIndex; + + m_currentWorkStatus = WorkStatus::Working; + + // 通知UI工作状态变更为"正在工作" + if (m_pStatus) { + m_pStatus->OnWorkStatusChanged(WorkStatus::Working); + } + + // 设置机械臂工作状态为忙碌 + if (m_pRobotProtocol) { + m_pRobotProtocol->SetWorkStatus(RobotProtocol::WORK_STATUS_BUSY); + } + + if(m_vrEyeDeviceList.empty()){ + LOG_ERROR("No camera device found\n"); + if (m_pStatus) { + m_pStatus->OnStatusUpdate("未找到相机设备"); + } + return ERR_CODE(DEV_NOT_FIND); + } + + // 清空检测数据缓存(释放之前的内存) + _ClearDetectionDataCache(); + + int nRet = SUCCESS; + + // 根据参数决定启动哪些相机 + + // 启动指定相机(cameraIndex为相机ID,从1开始编号) + int arrayIndex = cameraIndex - 1; // 转换为数组索引(从0开始) + + // 检查相机是否连接 + if (arrayIndex < 0 || arrayIndex >= static_cast(m_vrEyeDeviceList.size()) || m_vrEyeDeviceList[arrayIndex].second == nullptr) { + LOG_ERROR("Camera %d is not connected\n", cameraIndex); + QString cameraName = (arrayIndex >= 0 && arrayIndex < static_cast(m_vrEyeDeviceList.size())) ? + QString::fromStdString(m_vrEyeDeviceList[arrayIndex].first) : QString("相机%1").arg(cameraIndex); + m_pStatus->OnStatusUpdate(QString("%1 未连接").arg(cameraName).toStdString()); + return ERR_CODE(DEV_NOT_FIND); + } + + if (arrayIndex >= 0 && arrayIndex < static_cast(m_vrEyeDeviceList.size())) { + + IVrEyeDevice* pDevice = m_vrEyeDeviceList[arrayIndex].second; + + EVzResultDataType eDataType = keResultDataType_Position; + if(m_projectType == ProjectType::DirectBag){ + eDataType = keResultDataType_PointXYZRGBA; + } + + pDevice->SetStatusCallback(&LapWeldPresenter::_StaticCameraNotify, this); + + // 开始 + nRet = pDevice->StartDetect(&LapWeldPresenter::_StaticDetectionCallback, eDataType, this); + LOG_INFO("Camera ID %d start detection nRet: %d\n", cameraIndex, nRet); + if (nRet == SUCCESS) { + QString cameraName = QString::fromStdString(m_vrEyeDeviceList[arrayIndex].first); + m_pStatus->OnStatusUpdate(QString("启动%1检测成功").arg(cameraName).toStdString()); + } else { + LOG_ERROR("Camera ID %d start detection failed with error: %d\n", cameraIndex, nRet); + QString cameraName = QString::fromStdString(m_vrEyeDeviceList[arrayIndex].first); + m_pStatus->OnStatusUpdate(QString("启动%1检测失败[%d]").arg(cameraName).arg(nRet).toStdString()); + } + } else { + LOG_ERROR("Invalid camera ID: %d, valid range is 1-%zu\n", cameraIndex, m_vrEyeDeviceList.size()); + m_pStatus->OnStatusUpdate(QString("无效的相机ID: %1,有效范围: 1-%2").arg(cameraIndex).arg(m_vrEyeDeviceList.size()).toStdString()); + nRet = ERR_CODE(DEV_NOT_FIND); + } + + return nRet; +} + +int LapWeldPresenter::StopDetection() +{ + LOG_INFO("Stop detection\n"); + + // 停止所有相机的检测 + for (size_t i = 0; i < m_vrEyeDeviceList.size(); ++i) { + IVrEyeDevice* pDevice = m_vrEyeDeviceList[i].second; + if (pDevice) { + int ret = pDevice->StopDetect(); + if (ret == 0) { + LOG_INFO("Camera %zu stop detection successfully\n", i + 1); + } else { + LOG_WARNING("Camera %zu stop detection failed, error code: %d\n", i + 1, ret); + } + } + } + + // 通知UI工作状态变更为"就绪"(如果设备连接正常) + if (m_pStatus) { + // 检查设备连接状态,决定停止后的状态 + if (m_bCameraConnected && (m_bRobotConnected || m_bSerialConnected)) { + m_currentWorkStatus = WorkStatus::Ready; + m_pStatus->OnWorkStatusChanged(WorkStatus::Ready); + } else { + m_currentWorkStatus = WorkStatus::Error; + m_pStatus->OnWorkStatusChanged(WorkStatus::Error); + } + m_pStatus->OnStatusUpdate("检测已停止"); + } + + // 设置机械臂工作状态为空闲 + if (m_pRobotProtocol) { + m_pRobotProtocol->SetWorkStatus(RobotProtocol::WORK_STATUS_IDLE); + } + + return 0; +} + +// 加载调试数据进行检测 +int LapWeldPresenter::LoadDebugDataAndDetect(const std::string& filePath) +{ + LOG_INFO("Loading debug data from file: %s\n", filePath.c_str()); + + m_currentWorkStatus = WorkStatus::Working; + + if (m_pStatus) { + m_pStatus->OnWorkStatusChanged(WorkStatus::Working); + std::string fileName = QFileInfo(QString::fromStdString(filePath)).fileName().toStdString(); + m_pStatus->OnStatusUpdate(QString("加载文件:%1").arg(fileName.c_str()).toStdString()); + } + + // 设置机械臂工作状态为忙碌 + if (m_pRobotProtocol) { + m_pRobotProtocol->SetWorkStatus(RobotProtocol::WORK_STATUS_BUSY); + } + + + int lineNum = 0; + float scanSpeed = 0.0f; + int maxTimeStamp = 0; + int clockPerSecond = 0; + + + int result = SUCCESS; + // 1. 清空现有的检测数据缓存 + _ClearDetectionDataCache(); + + { + std::lock_guard lock(m_detectionDataMutex); + // 使用统一的LoadLaserScanData接口,自动判断文件格式 + result = m_dataLoader.LoadLaserScanData(filePath, m_detectionDataCache, lineNum, scanSpeed, maxTimeStamp, clockPerSecond); + } + + if (result != SUCCESS) { + LOG_ERROR("Failed to load debug data: %s\n", m_dataLoader.GetLastError().c_str()); + if (m_pStatus) { + m_pStatus->OnStatusUpdate("调试数据加载失败: " + m_dataLoader.GetLastError()); + } + return result; + } + + LOG_INFO("Successfully loaded %d lines of debug data\n", lineNum); + if (m_pStatus) { + m_pStatus->OnStatusUpdate(QString("成功加载 %1 行调试数据").arg(lineNum).toStdString()); + } + + // 等待检测完成 + result = _DetectTask(); + + return result; +} + +// 为所有相机设置状态回调 +void LapWeldPresenter::SetCameraStatusCallback(VzNL_OnNotifyStatusCBEx fNotify, void* param) +{ + for (size_t i = 0; i < m_vrEyeDeviceList.size(); i++) { + IVrEyeDevice* pDevice = m_vrEyeDeviceList[i].second; + if (pDevice) { + pDevice->SetStatusCallback(fNotify, param); + LOG_DEBUG("Status callback set for camera %zu\n", i + 1); + } + } +} + +// 打开相机 +int LapWeldPresenter::_OpenDevice(int cameraIndex, const char* cameraName, const char* cameraIp, ProjectType& projectType) +{ + IVrEyeDevice* pDevice = nullptr; + IVrEyeDevice::CreateObject(&pDevice); + int nRet = pDevice->InitDevice(); + ERR_CODE_RETURN(nRet); + + // 先设置状态回调 + nRet = pDevice->SetStatusCallback(&LapWeldPresenter::_StaticCameraNotify, this); + LOG_DEBUG("SetStatusCallback result: %d\n", nRet); + ERR_CODE_RETURN(nRet); + + // 尝试打开相机1 + nRet = pDevice->OpenDevice(cameraIp, ProjectType::DirectBag == projectType); + // 通过回调更新相机1状态 + bool cameraConnected = (SUCCESS == nRet); + if(!cameraConnected){ + delete pDevice; // 释放失败的设备 + pDevice = nullptr; + } + + int arrIdx = cameraIndex - 1; + + if(m_vrEyeDeviceList.size() > arrIdx){ + m_vrEyeDeviceList[arrIdx] = std::make_pair(cameraName, pDevice); // 直接存储到索引0 + } + + m_pStatus->OnCamera1StatusChanged(cameraConnected); + m_pStatus->OnStatusUpdate(cameraConnected ? "相机连接成功" : "相机连接失败"); + m_bCameraConnected = cameraConnected; + return nRet; +} + + +// 判断是否可以开始检测 +bool LapWeldPresenter::_SinglePreDetection(int cameraIndex) +{ + if(m_vrEyeDeviceList.empty()){ + LOG_ERROR("No camera device found\n"); + if (nullptr != m_pStatus) { + m_pStatus->OnStatusUpdate("未找到相机设备"); + } + return false; + } + + if(cameraIndex < 1 || cameraIndex > static_cast(m_vrEyeDeviceList.size())){ + LOG_ERROR("Invalid camera index: %d, valid range: 1-%zu\n", cameraIndex, m_vrEyeDeviceList.size()); + return false; + } + + if(m_vrEyeDeviceList[cameraIndex - 1].second == nullptr){ + LOG_ERROR("Camera %d is not connected\n", cameraIndex); + return false; + } + + return true; +} + +int LapWeldPresenter::_SingleDetection(int cameraIndex, bool isStart) +{ + int nRet = SUCCESS; + if (isStart) { + QString cameraName = (cameraIndex >= 1 && cameraIndex <= static_cast(m_vrEyeDeviceList.size())) ? + QString::fromStdString(m_vrEyeDeviceList[cameraIndex - 1].first) : QString("相机%1").arg(cameraIndex); + QString message = QString("收到信号,启动%1检测").arg(cameraName); + if (nullptr != m_pStatus) { + m_pStatus->OnStatusUpdate(message.toStdString()); + } + nRet = StartDetection(cameraIndex); + } else { + QString cameraName = (cameraIndex >= 1 && cameraIndex <= static_cast(m_vrEyeDeviceList.size())) ? + QString::fromStdString(m_vrEyeDeviceList[cameraIndex - 1].first) : QString("相机%1").arg(cameraIndex); + QString message = QString("收到信号,停止%1检测").arg(cameraName); + if (nullptr != m_pStatus) { + m_pStatus->OnStatusUpdate(message.toStdString()); + } + nRet = StopDetection(); + } + return nRet; +} + + +// 静态回调函数实现 +void LapWeldPresenter::_StaticCameraNotify(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pInfoParam) +{ + // 从pInfoParam获取this指针,转换回LapWeldPresenter*类型 + LapWeldPresenter* pThis = reinterpret_cast(pInfoParam); + if (pThis) + { + // 调用实例的非静态成员函数 + pThis->_CameraNotify(eStatus, pExtData, nDataLength, pInfoParam); + } +} + +void LapWeldPresenter::_CameraNotify(EVzDeviceWorkStatus eStatus, void *pExtData, unsigned int nDataLength, void *pInfoParam) +{ + LOG_DEBUG("[Camera Notify] received: status=%d\n", (int)eStatus); + + switch (eStatus) { + case EVzDeviceWorkStatus::keDeviceWorkStatus_Offline: + { + LOG_WARNING("[Camera Notify] Camera device offline/disconnected\n"); + + // 更新相机连接状态 + m_bCameraConnected = false; + + // 通知UI相机状态变更 + if (m_pStatus) { + // 这里需要判断是哪个相机离线,暂时更新相机1状态 + // 实际应用中可能需要通过pInfoParam或其他方式区分具体哪个相机 + m_pStatus->OnCamera1StatusChanged(false); + m_pStatus->OnStatusUpdate("相机设备离线"); + } + + // 检查并更新工作状态 + CheckAndUpdateWorkStatus(); + break; + } + + case EVzDeviceWorkStatus::keDeviceWorkStatus_Eye_Reconnect: + { + LOG_INFO("[Camera Notify] Camera device online/connected\n"); + + // 更新相机连接状态 + m_bCameraConnected = true; + + // 通知UI相机状态变更 + if (m_pStatus) { + m_pStatus->OnCamera1StatusChanged(true); + m_pStatus->OnStatusUpdate("相机设备已连接"); + } + + // 检查并更新工作状态 + CheckAndUpdateWorkStatus(); + break; + } + + case EVzDeviceWorkStatus::keDeviceWorkStatus_Device_Swing_Finish: + { + LOG_INFO("[Camera Notify] Received scan finish signal from camera\n"); + + // 发送页面提示信息 + if (m_pStatus) { + m_pStatus->OnStatusUpdate("相机扫描完成,开始数据处理..."); + } + + // 通知检测线程开始处理 + m_algoDetectCondition.notify_one(); + break; + } + + default: + break; + } +} + +// 检测数据回调函数静态版本 +void LapWeldPresenter::_StaticDetectionCallback(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint, void* pUserData) +{ + LapWeldPresenter* pThis = reinterpret_cast(pUserData); + if (pThis) { + pThis->_DetectionCallback(eDataType, pLaserLinePoint, pUserData); + } +} + +// 检测数据回调函数实例版本 +void LapWeldPresenter::_DetectionCallback(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint, void* pUserData) +{ + if (!pLaserLinePoint) { + LOG_WARNING("[Detection Callback] pLaserLinePoint is null\n"); + return; + } + + if (pLaserLinePoint->nPointCount <= 0) { + LOG_WARNING("[Detection Callback] Point count is zero or negative: %d\n", pLaserLinePoint->nPointCount); + return; + } + + if (!pLaserLinePoint->p3DPoint) { + LOG_WARNING("[Detection Callback] p3DPoint is null\n"); + return; + } + + // 直接存储SVzLaserLineData到统一缓存中 + SVzLaserLineData lineData; + memset(&lineData, 0, sizeof(SVzLaserLineData)); + + // 根据数据类型分配和复制点云数据 + if (eDataType == keResultDataType_Position) { + // 复制SVzNL3DPosition数据 + if (pLaserLinePoint->p3DPoint && pLaserLinePoint->nPointCount > 0) { + lineData.p3DPoint = new SVzNL3DPosition[pLaserLinePoint->nPointCount]; + if (lineData.p3DPoint) { + memcpy(lineData.p3DPoint, pLaserLinePoint->p3DPoint, sizeof(SVzNL3DPosition) * pLaserLinePoint->nPointCount); + } + lineData.p2DPoint = new SVzNL2DPosition[pLaserLinePoint->nPointCount]; + if (lineData.p2DPoint) { + memcpy(lineData.p2DPoint, pLaserLinePoint->p2DPoint, sizeof(SVzNL2DPosition) * pLaserLinePoint->nPointCount); + } + } + } else if (eDataType == keResultDataType_PointXYZRGBA) { + // 复制SVzNLPointXYZRGBA数据 + if (pLaserLinePoint->p3DPoint && pLaserLinePoint->nPointCount > 0) { + lineData.p3DPoint = new SVzNLPointXYZRGBA[pLaserLinePoint->nPointCount]; + if (lineData.p3DPoint) { + memcpy(lineData.p3DPoint, pLaserLinePoint->p3DPoint, sizeof(SVzNLPointXYZRGBA) * pLaserLinePoint->nPointCount); + } + lineData.p2DPoint = new SVzNL2DLRPoint[pLaserLinePoint->nPointCount]; + if (lineData.p2DPoint) { + memcpy(lineData.p2DPoint, pLaserLinePoint->p2DPoint, sizeof(SVzNL2DLRPoint) * pLaserLinePoint->nPointCount); + } + } + } + + + lineData.nPointCount = pLaserLinePoint->nPointCount; + lineData.llTimeStamp = pLaserLinePoint->llTimeStamp; + lineData.llFrameIdx = pLaserLinePoint->llFrameIdx; + lineData.nEncodeNo = pLaserLinePoint->nEncodeNo; + lineData.fSwingAngle = pLaserLinePoint->fSwingAngle; + lineData.bEndOnceScan = pLaserLinePoint->bEndOnceScan; + + std::lock_guard lock(m_detectionDataMutex); + m_detectionDataCache.push_back(std::make_pair(eDataType, lineData)); +} + +void LapWeldPresenter::CheckAndUpdateWorkStatus() +{ + if (m_bCameraConnected && (m_bRobotConnected || m_bSerialConnected)) { + m_currentWorkStatus = WorkStatus::Ready; + m_pStatus->OnWorkStatusChanged(WorkStatus::Ready); + } else { + m_currentWorkStatus = WorkStatus::Error; + m_pStatus->OnWorkStatusChanged(WorkStatus::Error); + } +} + +void LapWeldPresenter::_AlgoDetectThread() +{ + while(m_bAlgoDetectThreadRunning) + { + std::unique_lock lock(m_algoDetectMutex); + m_algoDetectCondition.wait(lock, [this]() { + return m_currentWorkStatus == WorkStatus::Working; + }); + + if(!m_bAlgoDetectThreadRunning){ + break; + } + + // 检查设备状态是否准备就绪 + int nRet = _DetectTask(); + + LOG_ERROR("DetectTask result: %d\n", nRet); + if(nRet != SUCCESS){ + m_pStatus->OnWorkStatusChanged(WorkStatus::Error); + + } + LOG_DEBUG("Algo Thread end\n"); + m_currentWorkStatus = WorkStatus::Ready; + } +} + +int LapWeldPresenter::_DetectTask() +{ + LOG_INFO("[Algo Thread] Start real detection task using algorithm\n"); + + std::lock_guard lock(m_detectionDataMutex); + // 1. 获取缓存的点云数据 + if (m_detectionDataCache.empty()) { + LOG_WARNING("No cached detection data available\n"); + if (m_pStatus) { + m_pStatus->OnStatusUpdate("无缓存的检测数据"); + } + return ERR_CODE(DEV_DATA_INVALID); + } + + // 2. 准备算法输入数据 + unsigned int lineNum = 0; + lineNum = m_detectionDataCache.size(); + if(m_pStatus){ + m_pStatus->OnStatusUpdate("扫描线数:" + std::to_string(lineNum) + ",正在算法检测..."); + } + + CVrTimeUtils oTimeUtils; + // 4. 根据当前相机索引获取对应的调平参数 + SSG_planeCalibPara currentCameraCalibParam = _GetCameraCalibParam(m_currentCameraIndex); + + // 获取当前使用的手眼标定矩阵 + const CalibMatrix currentClibMatrix = GetClibMatrix(m_currentCameraIndex - 1); + + DetectionResult detectionResult; + + int nRet = m_pDetectPresenter->DetectLapWeld(m_currentCameraIndex, m_detectionDataCache, m_projectType, m_algoParam, + currentCameraCalibParam, m_debugParam, m_dataLoader, currentClibMatrix.clibMatrix, detectionResult); + // 根据项目类型选择处理方式 + if (m_pStatus) { + QString err = QString("错误:%1").arg(nRet); + m_pStatus->OnStatusUpdate(QString("检测%1").arg(SUCCESS == nRet ? "成功": err).toStdString()); + } + + ERR_CODE_RETURN(nRet); + + LOG_INFO("[Algo Thread] sx_getLapWeldPostion detected %zu objects time : %.2f ms\n", detectionResult.positions.size(), oTimeUtils.GetElapsedTimeInMilliSec()); + + // 8. 返回检测结果 + detectionResult.cameraIndex = m_currentCameraIndex; + // 调用检测结果回调函数 + m_pStatus->OnDetectionResult(detectionResult); + + // 更新状态 + QString statusMsg = QString("检测完成,发现%1个目标").arg(detectionResult.positions.size()); + m_pStatus->OnStatusUpdate(statusMsg.toStdString()); + + // 更新机械臂协议状态(发送转换后的目标位置数据) + _SendDetectionResultToRobot(detectionResult, m_currentCameraIndex); + + // 9. 检测完成后,将工作状态更新为"完成" + if (m_pStatus) { + m_currentWorkStatus = WorkStatus::Completed; + m_pStatus->OnWorkStatusChanged(WorkStatus::Completed); + } + + // 设置机械臂工作状态为相应相机工作完成(相机ID从1开始) + if (m_pRobotProtocol) { + uint16_t workStatus = (m_currentCameraIndex == 1) ? + RobotProtocol::WORK_STATUS_CAMERA1_DONE : + RobotProtocol::WORK_STATUS_CAMERA2_DONE; + m_pRobotProtocol->SetWorkStatus(workStatus); + } + + // 恢复到就绪状态 + m_currentWorkStatus = WorkStatus::Ready; + + return SUCCESS; +} + +// 释放缓存的检测数据 +void LapWeldPresenter::_ClearDetectionDataCache() +{ + std::lock_guard lock(m_detectionDataMutex); + + // 释放加载的数据 + m_dataLoader.FreeLaserScanData(m_detectionDataCache); + LOG_DEBUG("Detection data cache cleared successfully\n"); +} + +// 发送检测结果给机械臂 +void LapWeldPresenter::_SendDetectionResultToRobot(const DetectionResult& detectionResult, int cameraIndex) +{ + LOG_INFO("Sending detection result for camera %d\n", cameraIndex); + + // 准备多目标数据结构 + MultiTargetData multiTargetData; + + // 检查是否有检测结果 + if (detectionResult.positions.empty()) { + LOG_INFO("No objects detected, sending empty result\n"); + // 发送空的多目标数据 + multiTargetData.count = 0; + multiTargetData.targets.clear(); + } else { + // 获取检测到的目标位置(已经是机械臂坐标系) + const auto& positions = detectionResult.positions; + multiTargetData.count = static_cast(positions.size()); + + // 直接使用已经转换好的机械臂坐标 + for (size_t i = 0; i < positions.size(); i++) { + auto& 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); + } + } + + // 发送到机械臂(网络ModbusTCP) + bool robotSent = false; + if (m_pRobotProtocol && (m_bRobotConnected || m_bSerialConnected)) { + int result = m_pRobotProtocol->SetMultiTargetData(multiTargetData, cameraIndex); + robotSent = (result == SUCCESS); + LOG_INFO("[Robot TCP] SetMultiTargetData result: %d for camera ID: %d\n", result, cameraIndex); + + if (m_pStatus) { + QString cameraName = (cameraIndex >= 1 && cameraIndex <= static_cast(m_vrEyeDeviceList.size())) ? + QString::fromStdString(m_vrEyeDeviceList[cameraIndex - 1].first) : QString("相机%1").arg(cameraIndex); + if (result != SUCCESS) { + m_pStatus->OnStatusUpdate(QString("写坐标给机械臂失败,%1").arg(cameraName).toStdString()); + } else { + m_pStatus->OnStatusUpdate(QString("写坐标给机械臂成功,%1").arg(cameraName).toStdString()); + } + } + } else { + LOG_WARNING("Robot TCP protocol not available\n"); + } + + // 发送到串口 + bool serialSent = false; + if (m_pSerialProtocol && m_bSerialConnected) { + + int result = m_pSerialProtocol->SendMultiTargetData(multiTargetData, cameraIndex); + serialSent = (result == SUCCESS); + LOG_INFO("[Serial] SendMultiTargetData result: %d for camera ID: %d\n", result, cameraIndex); + + if (m_pStatus) { + QString cameraName = (cameraIndex >= 1 && cameraIndex <= static_cast(m_vrEyeDeviceList.size())) ? + QString::fromStdString(m_vrEyeDeviceList[cameraIndex - 1].first) : QString("相机%1").arg(cameraIndex); + if (result != SUCCESS) { + m_pStatus->OnStatusUpdate(QString("写坐标给串口失败,%1").arg(cameraName).toStdString()); + } else { + m_pStatus->OnStatusUpdate(QString("写坐标给串口成功,%1").arg(cameraName).toStdString()); + } + } + } else { + LOG_WARNING("Serial protocol not available\n"); + } + + // 总结发送状态 + if (robotSent || serialSent) { + LOG_INFO("Detection result sent successfully (Robot TCP: %s, Serial: %s)\n", + robotSent ? "Yes" : "No", serialSent ? "Yes" : "No"); + } else { + LOG_WARNING("Failed to send detection result via any protocol\n"); + } +} + +// 实现配置改变通知接口 +void LapWeldPresenter::OnConfigChanged(const ConfigResult& configResult) +{ + LOG_INFO("Configuration changed notification received, reloading algorithm parameters\n"); + + // 更新调试参数 + m_debugParam = configResult.debugParam; + + // 重新初始化算法参数 + int result = InitAlgorithmParams(); + if (result == SUCCESS) { + LOG_INFO("Algorithm parameters reloaded successfully after config change\n"); + if (m_pStatus) { + m_pStatus->OnStatusUpdate("配置已更新,算法参数重新加载成功"); + } + } else { + LOG_ERROR("Failed to reload algorithm parameters after config change, error: %d\n", result); + if (m_pStatus) { + m_pStatus->OnStatusUpdate("配置更新后算法参数重新加载失败"); + } + } +} + +// 根据相机索引获取调平参数 +SSG_planeCalibPara LapWeldPresenter::_GetCameraCalibParam(int cameraIndex) +{ + // 查找指定相机索引的调平参数 + SSG_planeCalibPara calibParam; + + // 使用单位矩阵(未校准状态) + double identityMatrix[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; + for (int i = 0; i < 9; i++) { + calibParam.planeCalib[i] = identityMatrix[i]; + calibParam.invRMatrix[i] = identityMatrix[i]; + } + calibParam.planeHeight = -1.0; // 使用默认高度 + + for (const auto& cameraParam : m_cameraCalibParams) { + if (cameraParam.cameraIndex == cameraIndex) { + + // 根据isCalibrated标志决定使用标定矩阵还是单位矩阵 + if (cameraParam.isCalibrated) { + // 使用实际的标定矩阵 + for (int i = 0; i < 9; i++) { + calibParam.planeCalib[i] = cameraParam.planeCalib[i]; + calibParam.invRMatrix[i] = cameraParam.invRMatrix[i]; + } + calibParam.planeHeight = cameraParam.planeHeight; + } + } + } + return calibParam; +} + +// 实现IConfigChangeListener接口 +void LapWeldPresenter::OnSystemConfigChanged(const SystemConfig& config) +{ + LOG_INFO("System configuration changed, applying new configuration\n"); + + // 更新内部配置状态 + if (m_vrConfig) { + // 可以选择性地重新初始化算法参数 + InitAlgorithmParams(); + } + + if (m_pStatus) { + m_pStatus->OnStatusUpdate("系统配置已更新"); + } +} + +void LapWeldPresenter::OnCameraParamChanged(int cameraIndex, const CameraUIParam& cameraParam) +{ + LOG_INFO("Camera %d parameters changed: expose=%.2f, gain=%.2f, frameRate=%.2f\n", + cameraIndex, cameraParam.exposeTime, cameraParam.gain, cameraParam.frameRate); + + // 应用相机参数到实际设备 + if (cameraIndex >= 1 && cameraIndex <= static_cast(m_vrEyeDeviceList.size())) { + IVrEyeDevice* device = m_vrEyeDeviceList[cameraIndex - 1].second; + if (device) { + + // 设置曝光时间 + if (cameraParam.exposeTime > 0) { + unsigned int exposeTime = static_cast(cameraParam.exposeTime); + int ret = device->SetEyeExpose(exposeTime); + if (ret == SUCCESS) { + LOG_INFO("Set expose time %.2f for camera %d successfully\n", cameraParam.exposeTime, cameraIndex); + } else { + LOG_ERROR("Failed to set expose time for camera %d, error: %d\n", cameraIndex, ret); + } + } + + // 设置增益 + if (cameraParam.gain > 0) { + unsigned int gain = static_cast(cameraParam.gain); + int ret = device->SetEyeGain(gain); + if (ret == SUCCESS) { + LOG_INFO("Set gain %.2f for camera %d successfully\n", cameraParam.gain, cameraIndex); + } else { + LOG_ERROR("Failed to set gain for camera %d, error: %d\n", cameraIndex, ret); + } + } + + // 设置帧率 + if (cameraParam.frameRate > 0) { + int frameRate = static_cast(cameraParam.frameRate); + int ret = device->SetFrame(frameRate); + if (ret == SUCCESS) { + LOG_INFO("Set frame rate %.2f for camera %d successfully\n", cameraParam.frameRate, cameraIndex); + } else { + LOG_ERROR("Failed to set frame rate for camera %d, error: %d\n", cameraIndex, ret); + } + } + + // 设置摆动速度 + if (cameraParam.swingSpeed > 0) { + float swingSpeed = static_cast(cameraParam.swingSpeed); + int ret = device->SetSwingSpeed(swingSpeed); + if (ret == SUCCESS) { + LOG_INFO("Set swing speed %.2f for camera %d successfully\n", cameraParam.swingSpeed, cameraIndex); + } else { + LOG_ERROR("Failed to set swing speed for camera %d, error: %d\n", cameraIndex, ret); + } + } + + // 设置摆动角度 + if (cameraParam.swingStartAngle != cameraParam.swingStopAngle) { + float startAngle = static_cast(cameraParam.swingStartAngle); + float stopAngle = static_cast(cameraParam.swingStopAngle); + int ret = device->SetSwingAngle(startAngle, stopAngle); + if (ret == SUCCESS) { + LOG_INFO("Set swing angle %.2f-%.2f for camera %d successfully\n", + cameraParam.swingStartAngle, cameraParam.swingStopAngle, cameraIndex); + } else { + LOG_ERROR("Failed to set swing angle for camera %d, error: %d\n", cameraIndex, ret); + } + } + } + } + + if (m_pStatus) { + QString cameraName = (cameraIndex >= 1 && cameraIndex <= static_cast(m_vrEyeDeviceList.size())) ? + QString::fromStdString(m_vrEyeDeviceList[cameraIndex - 1].first) : QString("相机%1").arg(cameraIndex); + m_pStatus->OnStatusUpdate(QString("%1参数已更新").arg(cameraName).toStdString()); + } +} + +void LapWeldPresenter::OnAlgorithmParamChanged(const VrAlgorithmParams& algorithmParams) +{ + LOG_INFO("Algorithm parameters changed, updating internal configuration\n"); + + // 更新算法参数 + // 这里可以重新初始化算法参数或直接更新内部状态 + InitAlgorithmParams(); + + if (m_pStatus) { + m_pStatus->OnStatusUpdate("算法参数已更新"); + } +} + +void LapWeldPresenter::OnSerialConnectionChanged(bool connected) +{ + if (connected) { + LOG_INFO("Serial connection established\n"); + m_bSerialConnected = true; + } else { + LOG_INFO("Serial connection lost\n"); + m_bSerialConnected = false; + } + + // 更新工作状态 + CheckAndUpdateWorkStatus(); +} + +bool LapWeldPresenter::OnSerialWorkSignal(bool startWork, int cameraIndex) +{ + LOG_INFO("Serial work signal received: %s, camera index: %d\n", startWork ? "start" : "stop", cameraIndex); + + if(!_SinglePreDetection(cameraIndex)){ + return false; + } + + int nRet = _SingleDetection(cameraIndex, startWork); + + if(nRet != SUCCESS){ + m_currentWorkStatus = WorkStatus::Ready; + if (m_pStatus) { + m_pStatus->OnWorkStatusChanged(WorkStatus::Error); + } + } + return nRet == SUCCESS; +} + +// 设置默认相机索引 +void LapWeldPresenter::SetDefaultCameraIndex(int cameraIndex) +{ + LOG_INFO("Setting default camera index from %d to %d\n", m_currentCameraIndex, cameraIndex); + + // 验证相机索引的有效性(cameraIndex是配置中的索引,从1开始) + if (cameraIndex < 1 || cameraIndex > static_cast(m_vrEyeDeviceList.size())) { + LOG_WARNING("Invalid camera index: %d, valid range: 1-%zu\n", cameraIndex, m_vrEyeDeviceList.size()); + if (m_pStatus) { + m_pStatus->OnStatusUpdate(QString("无效的相机索引: %1,有效范围: 1-%2").arg(cameraIndex).arg(m_vrEyeDeviceList.size()).toStdString()); + } + return; + } + + // 更新默认相机索引 + m_currentCameraIndex = cameraIndex; + + LOG_INFO("Default camera index updated to %d\n", m_currentCameraIndex); + + if (m_pStatus) { + QString cameraName = (cameraIndex >= 1 && cameraIndex <= static_cast(m_vrEyeDeviceList.size())) ? + QString::fromStdString(m_vrEyeDeviceList[cameraIndex - 1].first) : QString("相机%1").arg(cameraIndex); + m_pStatus->OnStatusUpdate(QString("设置%1为默认相机").arg(cameraName).toStdString()); + } +} + +// 保存检测数据到文件(默认实现) +int LapWeldPresenter::SaveDetectionDataToFile(const std::string& filePath) +{ + LOG_INFO("Saving detection data to file: %s\n", filePath.c_str()); + + if (m_detectionDataCache.empty()) { + LOG_WARNING("No detection data available for saving\n"); + return ERR_CODE(DEV_DATA_INVALID); + } + + // 保存数据到文件 + int lineNum = static_cast(m_detectionDataCache.size()); + float scanSpeed = 0.0f; + int maxTimeStamp = 0; + int clockPerSecond = 0; + + int result = m_dataLoader.SaveLaserScanData(filePath, m_detectionDataCache, lineNum, scanSpeed, maxTimeStamp, clockPerSecond); + + if (result == SUCCESS) { + LOG_INFO("Successfully saved %d lines of detection data to file: %s\n", lineNum, filePath.c_str()); + } else { + LOG_ERROR("Failed to save detection data, error: %s\n", m_dataLoader.GetLastError().c_str()); + } + + return result; +} + + diff --git a/App/LapWeld/LapWeldApp/Presenter/Src/RobotProtocol.cpp b/App/LapWeld/LapWeldApp/Presenter/Src/RobotProtocol.cpp new file mode 100644 index 0000000..9a6a1d0 --- /dev/null +++ b/App/LapWeld/LapWeldApp/Presenter/Src/RobotProtocol.cpp @@ -0,0 +1,254 @@ +#include "RobotProtocol.h" +#include "VrLog.h" +#include "VrError.h" +#include +#include + +RobotProtocol::RobotProtocol() + : m_pModbusServer(nullptr) + , m_bServerRunning(false) + , m_nPort(502) + , m_robotStatus(STATUS_DISCONNECTED) +{ +} + +RobotProtocol::~RobotProtocol() +{ + Deinitialize(); +} + +int RobotProtocol::Initialize(uint16_t port) +{ + if (m_bServerRunning) { + LOG_WARNING("Server is already running\n"); + return SUCCESS; + } + + m_nPort = port; + + // 创建ModbusTCP服务器实例 + bool bRet = IYModbusTCPServer::CreateInstance(&m_pModbusServer); + LOG_DEBUG("Create ModbusTCP server %s \n", bRet ? "success" : "failed"); + m_bServerRunning = bRet; + + // 设置ModbusTCP回调函数 + if (m_pModbusServer) { + // 设置写线圈回调 + m_pModbusServer->setWriteCoilsCallback([this](uint8_t unitId, uint16_t startAddress, uint16_t quantity, const uint8_t* values) { + return this->OnWriteCoils(unitId, startAddress, quantity, values); + }); + + // 设置写寄存器回调 + m_pModbusServer->setWriteRegistersCallback([this](uint8_t unitId, uint16_t startAddress, uint16_t quantity, const uint16_t* values) { + return this->OnWriteRegisters(unitId, startAddress, quantity, values); + }); + + // 设置连接状态回调 + m_pModbusServer->setConnectionStatusCallback([this](bool connected) { + this->OnModbusTCPConnectionChanged(connected); + }); + } + + int nRet = m_pModbusServer->start(m_nPort); + ERR_CODE_RETURN(nRet); + + // 设置初始状态 + m_robotStatus = STATUS_CONNECTED; + + // 设置初始工作状态为空闲 + SetWorkStatus(WORK_STATUS_IDLE); + + LOG_INFO("ModbusTCP service initialization completed\n"); + return SUCCESS; +} + +void RobotProtocol::Deinitialize() +{ + LOG_DEBUG("Stop ModbusTCP service\n"); + + // 停止ModbusTCP服务器 + StopModbusTCPServer(); + + // 重置状态 + m_robotStatus = STATUS_DISCONNECTED; + m_bServerRunning = false; + + LOG_INFO("ModbusTCP service stopped\n"); +} + +int RobotProtocol::SetMultiTargetData(const MultiTargetData& multiTargetData, uint16_t cameraId) +{ + LOG_DEBUG("Set multi-target data, count: %d, camera ID: %d\n", multiTargetData.count, cameraId); + + // 验证相机ID有效性(从1开始编号:1,2,...) + if (cameraId < 1 || cameraId > 2) { + LOG_ERROR("Invalid camera ID: %d, valid range is 1-2\n", cameraId); + return -1; + } + + // 数据格式: count + 每个目标的{x,y,z,rz}坐标(每个坐标占2个寄存器) + std::vector data; + // 限制最大目标数量 + uint16_t actualCount = multiTargetData.count > 5 ? 5 : multiTargetData.count; + data.resize(actualCount * 8 + 1); + + // 根据相机ID确定起始地址(cameraId: 1->COORD_ONE, 2->COORD_TWO) + uint16_t startAddr = (cameraId == 1) ? COORD_ONE_START_ADDR : COORD_TWO_START_ADDR; + + // 设置目标数量 + data[0] = actualCount; + + // 从第二个寄存器开始存储坐标数据 count [{x,y,z,rz}, {x,y,z,rz}] + uint16_t dataIndex = 2; + + // 定义一个辅助函数来转换float到ModbusTCP寄存器顺序大端格式 + auto floatToBigEndian = [&](float value) { + uint32_t intValue; + memcpy(&intValue, &value, sizeof(float)); + // ModbusTCP寄存器顺序大端:高16位寄存器在前,低16位寄存器在后 + // 每个寄存器内部保持主机字节序 + data[dataIndex++] = (intValue >> 16) & 0xFFFF; // 高16位寄存器 + data[dataIndex++] = intValue & 0xFFFF; // 低16位寄存器 + }; + + for (uint16_t i = 0; i < actualCount; i++) { + const TargetPosition& target = multiTargetData.targets[i]; + + // 按照 x, y, z, rz 的顺序存储每个目标的坐标 + floatToBigEndian(target.x); // X坐标 (2个寄存器) + floatToBigEndian(target.y); // Y坐标 (2个寄存器) + floatToBigEndian(target.z); // Z坐标 (2个寄存器) + floatToBigEndian(target.rz); // RZ坐标 (2个寄存器) + } + + m_pModbusServer->updateHoldingRegisters(startAddr, data); + + return 0; +} + +RobotProtocol::RobotStatus RobotProtocol::GetDetectionStatus() const +{ + return m_robotStatus; +} + +void RobotProtocol::SetConnectionCallback(const ConnectionCallback& callback) +{ + m_connectionCallback = callback; +} + +void RobotProtocol::SetWorkSignalCallback(const WorkSignalCallback& callback) +{ + m_workSignalCallback = callback; +} + +bool RobotProtocol::IsRunning() const +{ + return m_bServerRunning; +} + +int RobotProtocol::SetWorkStatus(uint16_t status) +{ + LOG_DEBUG("Set work status to: %d\n", status); + + if (!m_pModbusServer) { + LOG_ERROR("ModbusTCP server not initialized\n"); + return -1; + } + + // 更新STATUS_ADDR地址的寄存器值 + std::vector statusData; + statusData.push_back(status); + + m_pModbusServer->updateHoldingRegisters(STATUS_ADDR, statusData); + + const char* statusStr = ""; + switch(status) { + case WORK_STATUS_IDLE: statusStr = "空闲"; break; + case WORK_STATUS_CAMERA1_DONE: statusStr = "相机1工作完成"; break; + case WORK_STATUS_CAMERA2_DONE: statusStr = "相机2工作完成"; break; + case WORK_STATUS_BUSY: statusStr = "忙碌"; break; + default: statusStr = "未知状态"; break; + } + + LOG_INFO("Work status updated to: %d (%s)\n", status, statusStr); + return 0; +} + +void RobotProtocol::StopModbusTCPServer() +{ + LOG_DEBUG("Stop ModbusTCP server\n"); + + if (m_pModbusServer) { + // 释放资源 + delete m_pModbusServer; + m_pModbusServer = nullptr; + } + + m_bServerRunning = false; + LOG_INFO("ModbusTCP server stopped\n"); +} + +IYModbusTCPServer::ErrorCode RobotProtocol::OnWriteCoils(uint8_t unitId, uint16_t startAddress, uint16_t quantity, const uint8_t* values) +{ + LOG_DEBUG("Write coils - UnitID:%d, StartAddress:%d, Quantity:%d\n", unitId, startAddress, quantity); + + // 处理工作信号线圈 + if (startAddress == WORK_SIGNAL_ADDR) { + bool workSignal = (values[0] >> 0) & 0x01; + + LOG_INFO("Received work signal: %s (coil address: %d)\n", + (workSignal ? "start work" : "stop work"), startAddress); + + // 触发工作信号回调(使用相机ID 1作为默认值) + if (m_workSignalCallback) { + m_workSignalCallback(workSignal, 1); // 默认相机ID为1 + } + } else { + LOG_WARNING("Unknown coil address: %d\n", startAddress); + return IYModbusTCPServer::ErrorCode::ILLEGAL_DATA_ADDRESS; + } + + return IYModbusTCPServer::ErrorCode::SUCCESS; +} + +IYModbusTCPServer::ErrorCode RobotProtocol::OnWriteRegisters(uint8_t unitId, uint16_t startAddress, uint16_t quantity, const uint16_t* values) +{ + LOG_DEBUG("Write registers - UnitID:%d, StartAddress:%d, Quantity:%d\n", unitId, startAddress, quantity); + + // 处理坐标寄存器更新 + if(quantity > 1) return IYModbusTCPServer::ErrorCode::ILLEGAL_DATA_VALUE; + + if(nullptr == m_workSignalCallback) return IYModbusTCPServer::ErrorCode::GATEWAY_TARGET_FAILED; + + IYModbusTCPServer::ErrorCode eResult = IYModbusTCPServer::ErrorCode::SUCCESS; + + switch (startAddress) { + case WORK_SIGNAL_ADDR: + // 解析工作信号寄存器 + if(!m_workSignalCallback(true, (int)values[0])){ + eResult = IYModbusTCPServer::ErrorCode::SERVER_FAILURE; + } + break; + default: + eResult = IYModbusTCPServer::ErrorCode::ILLEGAL_FUNCTION; + break; + } + + return eResult; +} + +void RobotProtocol::OnModbusTCPConnectionChanged(bool connected) +{ + if (connected) { + LOG_INFO("ModbusTCP connection established\n"); + m_robotStatus = STATUS_CONNECTED; + } else { + LOG_INFO("ModbusTCP connection lost\n"); + m_robotStatus = STATUS_DISCONNECTED; + } + + // 触发上层连接状态回调 + if (m_connectionCallback) { + m_connectionCallback(connected); + } +} diff --git a/App/LapWeld/LapWeldApp/Presenter/Src/SerialProtocol.cpp b/App/LapWeld/LapWeldApp/Presenter/Src/SerialProtocol.cpp new file mode 100644 index 0000000..07c05ba --- /dev/null +++ b/App/LapWeld/LapWeldApp/Presenter/Src/SerialProtocol.cpp @@ -0,0 +1,550 @@ +#include "SerialProtocol.h" +#include "VrLog.h" +#include "VrError.h" +#include +#include +#include +#include +#include +#include +#include + +#include + +// 协议常量定义 +const QString SerialProtocol::PROTOCOL_START = "$"; +const QString SerialProtocol::PROTOCOL_END = "#"; +const QString SerialProtocol::CMD_START = "START"; +const QString SerialProtocol::CMD_DATA = "DATA"; +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(); +} + +int SerialProtocol::OpenSerial(const std::string& portName, int baudRate, + int dataBits, int stopBits, int parity, int flowControl) +{ + if (m_pSerialPort->isOpen()) { + LOG_WARNING("Serial port is already open: %s\n", portName.c_str()); + return SUCCESS; + } + + LOG_INFO("Opening serial port: %s, baudRate: %d, dataBits: %d, stopBits: %d, parity: %d, flowControl: %d\n", + portName.c_str(), baudRate, dataBits, stopBits, parity, flowControl); + + // 设置串口参数 + m_pSerialPort->setPortName(QString::fromStdString(portName)); + m_pSerialPort->setBaudRate(baudRate); + + // 设置数据位 + switch (dataBits) { + case 5: m_pSerialPort->setDataBits(QSerialPort::Data5); break; + case 6: m_pSerialPort->setDataBits(QSerialPort::Data6); break; + case 7: m_pSerialPort->setDataBits(QSerialPort::Data7); break; + case 8: m_pSerialPort->setDataBits(QSerialPort::Data8); break; + default: m_pSerialPort->setDataBits(QSerialPort::Data8); break; + } + + // 设置停止位 + switch (stopBits) { + case 1: m_pSerialPort->setStopBits(QSerialPort::OneStop); break; + case 2: m_pSerialPort->setStopBits(QSerialPort::TwoStop); break; + default: m_pSerialPort->setStopBits(QSerialPort::OneStop); break; + } + + // 设置校验位 + switch (parity) { + case 0: m_pSerialPort->setParity(QSerialPort::NoParity); break; + case 1: m_pSerialPort->setParity(QSerialPort::OddParity); break; + case 2: m_pSerialPort->setParity(QSerialPort::EvenParity); break; + default: m_pSerialPort->setParity(QSerialPort::NoParity); break; + } + + // 设置流控制 + switch (flowControl) { + case 0: m_pSerialPort->setFlowControl(QSerialPort::NoFlowControl); break; + case 1: m_pSerialPort->setFlowControl(QSerialPort::HardwareControl); break; + case 2: m_pSerialPort->setFlowControl(QSerialPort::SoftwareControl); break; + default: m_pSerialPort->setFlowControl(QSerialPort::NoFlowControl); break; + } + + // 尝试打开串口 + if (!m_pSerialPort->open(QIODevice::ReadWrite)) { + LOG_ERROR("Failed to open serial port: %s, error: %s\n", portName.c_str(), m_pSerialPort->errorString().toStdString().c_str()); + m_serialStatus = STATUS_ERROR; + return ERR_CODE(DEV_OPEN_ERR); + } + + // 清空接收缓冲区 + m_receiveBuffer.clear(); + m_pSerialPort->clear(); + + // 设置读取超时和缓冲区大小 + m_pSerialPort->setReadBufferSize(1024); + + m_serialStatus = STATUS_CONNECTED; + LOG_INFO("Serial port opened successfully: %s\n", portName.c_str()); + + // 启动接收线程 + StartReceiveThread(); + + // 触发连接状态回调 + if (m_connectionCallback) { + m_connectionCallback(true); + } + + return SUCCESS; +} + +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; + + // 触发连接状态回调 + if (m_connectionCallback) { + m_connectionCallback(false); + } + } +} + +int SerialProtocol::SendMultiTargetData(const MultiTargetData& multiTargetData, uint16_t cameraId) +{ + if (!m_pSerialPort || !m_pSerialPort->isOpen()) { + LOG_ERROR("Serial port is not open, cannot send data\n"); + return -1; + } + + LOG_DEBUG("Sending multi-target data, count: %d, camera ID: %d\n", multiTargetData.count, cameraId); + + // 构造协议数据 + std::string protocolData = BuildDataProtocol(multiTargetData, cameraId); + + // 发送数据 + int result = SendData(protocolData); + if (result == SUCCESS) { + LOG_INFO("Multi-target data sent successfully: %s\n", protocolData.c_str()); + } else { + LOG_ERROR("Failed to send multi-target data\n"); + } + + return result; +} + +SerialProtocol::SerialStatus SerialProtocol::GetSerialStatus() const +{ + return m_serialStatus; +} + +void SerialProtocol::SetConnectionCallback(const ConnectionCallback& callback) +{ + m_connectionCallback = callback; +} + +void SerialProtocol::SetWorkSignalCallback(const WorkSignalCallback& callback) +{ + m_workSignalCallback = callback; +} + +bool SerialProtocol::IsOpen() const +{ + return m_pSerialPort && m_pSerialPort->isOpen(); +} + +void SerialProtocol::OnSerialDataReceived() +{ + // 注意:这个方法现在主要作为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("[Signal] Serial port is null in OnSerialDataReceived\n"); + return; + } + + if (!m_pSerialPort->isOpen()) { + LOG_ERROR("[Signal] Serial port is not open in OnSerialDataReceived\n"); + return; + } + + // 检查可用字节数 + qint64 bytesAvailable = m_pSerialPort->bytesAvailable(); + LOG_INFO("[Signal] Bytes available to read: %lld\n", bytesAvailable); + + if (bytesAvailable <= 0) { + LOG_WARNING("[Signal] No bytes available but readyRead signal was triggered\n"); + return; + } + + // 读取所有可用数据 + QByteArray data = m_pSerialPort->readAll(); + QString receivedData = QString::fromUtf8(data); + + LOG_INFO("[Signal] Actually read %d bytes from serial port\n", data.size()); + + if (data.isEmpty()) { + LOG_WARNING("[Signal] readAll() returned empty data\n"); + return; + } + + // 使用互斥锁保护缓冲区 + { + std::lock_guard lock(m_bufferMutex); + m_receiveBuffer += receivedData; + } + + // 打印接收到的数据 + LOG_INFO("[Signal] Received [%d bytes]: \"%s\"\n", data.size(), receivedData.toStdString().c_str()); + + // 如果包含不可打印字符,额外打印ASCII码 + bool hasNonPrintable = false; + for (char c : data) { + if (c < 32 || c > 126) { + hasNonPrintable = true; + break; + } + } + + if (hasNonPrintable) { + QString asciiInfo = "ASCII: "; + for (char c : data) { + if (c >= 32 && c <= 126) { + asciiInfo += QString("'%1' ").arg(c); + } else { + asciiInfo += QString("[%1] ").arg((unsigned char)c); + } + } + LOG_INFO("[Signal] %s\n", asciiInfo.toStdString().c_str()); + } + + // 处理接收到的数据 + ProcessReceivedData(); +} + +void SerialProtocol::OnSerialError(QSerialPort::SerialPortError error) +{ + if (error == QSerialPort::NoError) { + return; + } + + QString errorString = m_pSerialPort ? m_pSerialPort->errorString() : "Unknown error"; + LOG_ERROR("=== SERIAL PORT ERROR ===\n"); + LOG_ERROR("Error type: %d\n", (int)error); + LOG_ERROR("Error message: %s\n", errorString.toStdString().c_str()); + LOG_ERROR("Port open status: %s\n", (m_pSerialPort && m_pSerialPort->isOpen()) ? "OPEN" : "CLOSED"); + + // 详细的错误类型说明 + switch (error) { + case QSerialPort::DeviceNotFoundError: + LOG_ERROR("Device not found error - port may be disconnected\n"); + break; + case QSerialPort::PermissionError: + LOG_ERROR("Permission error - port may be in use by another application\n"); + break; + case QSerialPort::OpenError: + LOG_ERROR("Open error - failed to open the port\n"); + break; + case QSerialPort::WriteError: + LOG_ERROR("Write error - failed to write data\n"); + break; + case QSerialPort::ReadError: + LOG_ERROR("Read error - failed to read data\n"); + break; + case QSerialPort::ResourceError: + LOG_ERROR("Resource error - device disappeared or became unavailable\n"); + break; + case QSerialPort::UnsupportedOperationError: + LOG_ERROR("Unsupported operation error\n"); + break; + case QSerialPort::TimeoutError: + LOG_ERROR("Timeout error\n"); + break; + default: + LOG_ERROR("Unknown error type: %d\n", (int)error); + break; + } + + // 设置错误状态 + m_serialStatus = STATUS_ERROR; + + // 根据错误类型处理 + switch (error) { + case QSerialPort::ResourceError: + case QSerialPort::DeviceNotFoundError: + case QSerialPort::PermissionError: + LOG_ERROR("Critical error - notifying connection callback\n"); + // 连接断开错误 + if (m_connectionCallback) { + m_connectionCallback(false); + } + break; + default: + LOG_WARNING("Non-critical error - continuing operation\n"); + break; + } + + LOG_ERROR("=== END SERIAL ERROR ===\n"); +} + +void SerialProtocol::ParseProtocolData(const QString& data) +{ + LOG_DEBUG("Parsing protocol data: %s\n", data.toStdString().c_str()); + + // 移除协议开始和结束标识 + QString cleanData = data; + cleanData.remove(PROTOCOL_START); + cleanData.remove(PROTOCOL_END); + + // 按分隔符分割数据 + QStringList parts = cleanData.split(SEPARATOR); + + // 移除空的部分 + 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 = validParts[0]; + LOG_DEBUG("Extracted command: '%s'\n", command.toStdString().c_str()); + + if (command == CMD_START) { + // 处理开始命令:$,START,# 或 $,START,cameraId,# + LOG_INFO("Received START command\n"); + + // 默认相机ID为1,如果有更多参数可以解析 + int cameraId = 1; + if (validParts.size() > 1) { + bool ok; + cameraId = validParts[1].toInt(&ok); + if (!ok) { + cameraId = 1; + } + } + + // 触发工作信号回调 + if (m_workSignalCallback) { + bool result = m_workSignalCallback(true, cameraId); + LOG_INFO("Work signal callback result: %s, camera ID: %d\n", result ? "success" : "failed", cameraId); + } + + m_serialStatus = STATUS_WORKING; + } else { + LOG_WARNING("Unknown command received: %s\n", command.toStdString().c_str()); + } +} + +int SerialProtocol::SendData(const std::string& data) +{ + if (!m_pSerialPort || !m_pSerialPort->isOpen()) { + LOG_ERROR("Serial port is not open, cannot send data\n"); + return -1; + } + + // 检查串口是否可写 + if (!m_pSerialPort->isWritable()) { + LOG_ERROR("Serial port is not writable\n"); + return -1; + } + + // 检查待写入的字节数 + qint64 bytesToWrite = m_pSerialPort->bytesToWrite(); + LOG_INFO("Bytes pending write before send: %lld\n", bytesToWrite); + + QByteArray dataToSend = data.c_str(); + qint64 bytesWritten = m_pSerialPort->write(dataToSend); + + LOG_INFO("Write operation result: %lld bytes (expected: %d)\n", bytesWritten, dataToSend.size()); + + if (bytesWritten == -1) { + LOG_ERROR("Failed to write data to serial port: %s\n", m_pSerialPort->errorString().toStdString().c_str()); + m_serialStatus = STATUS_ERROR; + return -1; + } + + if (bytesWritten != dataToSend.size()) { + LOG_WARNING("PARTIAL WRITE: %lld of %d bytes written\n", bytesWritten, dataToSend.size()); + } + + // 立即刷新缓冲区 + m_pSerialPort->flush(); + + + LOG_DEBUG("Sent %lld bytes to serial port\n", bytesWritten); + return SUCCESS; +} + +std::string SerialProtocol::BuildDataProtocol(const MultiTargetData& multiTargetData, uint16_t cameraId) +{ + std::stringstream ss; + + // 协议格式:$,DATA,NUM,posX,posY,posZ,posC,posX,posY,posZ,posC...,# + ss << PROTOCOL_START.toStdString() << SEPARATOR.toStdString() + << CMD_DATA.toStdString() << SEPARATOR.toStdString() + << multiTargetData.count; + + // 添加每个目标的坐标数据 + for (const auto& target : multiTargetData.targets) { + ss << SEPARATOR.toStdString() + << std::fixed << std::setprecision(2) << target.x << SEPARATOR.toStdString() + << std::fixed << std::setprecision(2) << target.y << SEPARATOR.toStdString() + << std::fixed << std::setprecision(2) << target.z << SEPARATOR.toStdString() + << std::fixed << std::setprecision(2) << target.rz; + } + + ss << SEPARATOR.toStdString() << PROTOCOL_END.toStdString(); + + 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/App/LapWeld/LapWeldApp/Utils/Inc/LaserDataLoader.h b/App/LapWeld/LapWeldApp/Utils/Inc/LaserDataLoader.h new file mode 100644 index 0000000..1c1c216 --- /dev/null +++ b/App/LapWeld/LapWeldApp/Utils/Inc/LaserDataLoader.h @@ -0,0 +1,66 @@ +#ifndef LASER_DATA_LOADER_H +#define LASER_DATA_LOADER_H + +#include +#include +#include "VZNL_Types.h" +#include "VrError.h" + +// 激光数据加载器类 +class LaserDataLoader +{ +public: + LaserDataLoader(); + ~LaserDataLoader(); + + // 从文件加载激光扫描数据 - 统一接口,根据文件格式自动判断数据类型 + int LoadLaserScanData(const std::string& fileName, + std::vector>& laserLines, + int& lineNum, + float& scanSpeed, + int& maxTimeStamp, + int& clockPerSecond); + + // 保存激光扫描数据到文件 - 统一接口,支持两种类型的数据 + int SaveLaserScanData(const std::string& fileName, + const std::vector>& laserLines, + int lineNum, + float scanSpeed, + int maxTimeStamp, + int clockPerSecond); + + // 释放统一格式数据内存 + void FreeLaserScanData(std::vector>& laserLines); + + // 转换统一格式数据为SVzNL3DLaserLine格式 + int ConvertToSVzNL3DLaserLine(const std::vector>& unifiedData, + std::vector& xyzData); + + // 转换统一格式数据为SVzNLXYZRGBDLaserLine格式 + int ConvertToSVzNLXYZRGBDLaserLine(const std::vector>& unifiedData, + std::vector& rgbdData); + + // 释放转换后的SVzNL3DLaserLine数据内存 + void FreeConvertedData(std::vector& xyzData); + + // 释放转换后的SVzNLXYZRGBDLaserLine数据内存 + void FreeConvertedData(std::vector& rgbdData); + + // 获取最后的错误信息 + std::string GetLastError() const { return m_lastError; } + +private: + // 读取XYZ格式的激光数据 + int _ParseLaserScanPoint(const std::string& data, SVzNL3DPosition& sData, SVzNL2DPosition& s2DData); + + // 读取RGBD格式的激光数据 - 新增RGBD支持 + int _ParseLaserScanPoint(const std::string& data, SVzNLPointXYZRGBA& sData, SVzNL2DLRPoint& s2DData); + + // 获取激光数据类型 + int _GetLaserType(const std::string& fileName, EVzResultDataType& eDataType); + + std::string m_lastError; + static const int VZ_LASER_LINE_PT_MAX_NUM = 4096; +}; + +#endif // LASER_DATA_LOADER_H \ No newline at end of file diff --git a/App/LapWeld/LapWeldApp/Utils/Inc/PathManager.h b/App/LapWeld/LapWeldApp/Utils/Inc/PathManager.h new file mode 100644 index 0000000..d6e012b --- /dev/null +++ b/App/LapWeld/LapWeldApp/Utils/Inc/PathManager.h @@ -0,0 +1,57 @@ +#ifndef PATHMANAGER_H +#define PATHMANAGER_H + +#include + +/** + * @brief 路径管理器类,统一管理应用程序的配置文件路径 + * + * 该类负责根据不同操作系统提供合适的配置文件存储路径: + * - Windows: 程序目录 + * Linux: 用户配置目录 (~/.config/LapWeld/) + */ +class PathManager +{ +public: + /** + * @brief 获取配置文件(config.xml)的完整路径 + * @return 配置文件的完整路径 + */ + static QString GetConfigFilePath(); + + /** + * @brief 获取手眼标定文件(clib.ini)的完整路径 + * @return 手眼标定文件的完整路径 + */ + static QString GetCalibrationFilePath(); + + +private: + + /** + * @brief 确保配置目录存在,如果不存在则创建 + * @return 成功创建或目录已存在返回true,失败返回false + */ + static bool EnsureConfigDirectoryExists(); + + /** + * @brief 获取应用程序配置目录路径 + * @return 配置目录的完整路径 + */ + static QString GetAppConfigDirectory(); + + + /** + * @brief 获取程序目录路径 + * @return 程序目录的完整路径 + */ + static QString GetProgramDirectory(); + + /** + * @brief 获取用户配置目录路径(仅Linux系统) + * @return 用户配置目录的完整路径 + */ + static QString GetUserConfigDirectory(); +}; + +#endif // PATHMANAGER_H \ No newline at end of file diff --git a/App/LapWeld/LapWeldApp/Utils/Inc/PointCloudImageUtils.h b/App/LapWeld/LapWeldApp/Utils/Inc/PointCloudImageUtils.h new file mode 100644 index 0000000..2dd9da2 --- /dev/null +++ b/App/LapWeld/LapWeldApp/Utils/Inc/PointCloudImageUtils.h @@ -0,0 +1,55 @@ +#ifndef POINTCLOUDIMAGEUTILS_H +#define POINTCLOUDIMAGEUTILS_H + +#include +#include +#include + +#include "SG_baseDataType.h" +#include "SX_lapWeldDetection_Export.h" + +class PointCloudImageUtils +{ +public: + // 点云转图像 - 从LapWeldPresenter提取 + static QImage GeneratePointCloudImage(SVzNL3DLaserLine* scanData, + int lineNum, + const std::vector& objOps); + + static QImage GeneratePointCloudImage(SVzNLXYZRGBDLaserLine* scanData, + int lineNum, + const std::vector& objOps); + + // 新的点云图像生成函数 - 基于X、Y范围创建图像 + static QImage GeneratePointCloudImage(SVzNLXYZRGBDLaserLine* scanData, + int lineNum); + +private: + // 定义线特征颜色和大小获取函数 + static void GetLineFeatureStyle(int vType, int hType, int objId, + QColor& pointColor, int& pointSize); + + // 获取对象颜色 + static QColor GetObjectColor(int index); + + // 计算点云范围 + static void CalculatePointCloudRange(SVzNL3DLaserLine* scanData, int lineNum, + double& xMin, double& xMax, + double& yMin, double& yMax); + + // 绘制目标检测结果 + static void DrawDetectionTargets(QPainter& painter, + const std::vector& objOps, + double xMin, double xScale, int xSkip, + double yMin, double yScale, int ySkip, + int imgCols, int imgRows); + + // 绘制目标检测结果 + static void DrawDetectionTargets(QPainter& painter, + const std::vector& objOps, + double xMin, double xScale, int xSkip, + double yMin, double yScale, int ySkip, + int imgCols, int imgRows); +}; + +#endif // POINTCLOUDIMAGEUTILS_H \ No newline at end of file diff --git a/App/LapWeld/LapWeldApp/Utils/Src/LaserDataLoader.cpp b/App/LapWeld/LapWeldApp/Utils/Src/LaserDataLoader.cpp new file mode 100644 index 0000000..70eea14 --- /dev/null +++ b/App/LapWeld/LapWeldApp/Utils/Src/LaserDataLoader.cpp @@ -0,0 +1,432 @@ +#include "LaserDataLoader.h" +#include +#include +#include +#include +#include +#include +#include "VrLog.h" +#include + +LaserDataLoader::LaserDataLoader() +{ + m_lastError.clear(); +} + +LaserDataLoader::~LaserDataLoader() +{ +} + +int LaserDataLoader::LoadLaserScanData(const std::string& fileName, + std::vector>& laserLines, + int& lineNum, + float& scanSpeed, + int& maxTimeStamp, + int& clockPerSecond) +{ + LOG_INFO("Loading laser scan data from file: %s\n", fileName.c_str()); + + // 清空输出参数 + laserLines.clear(); + lineNum = 0; + scanSpeed = 0.0f; + maxTimeStamp = 0; + clockPerSecond = 0; + + // 判断文件类型 + std::ifstream inputFile(fileName); + if (!inputFile.is_open()) { + m_lastError = "Cannot open file: " + fileName; + LOG_ERROR("Cannot open file: %s\n", fileName.c_str()); + return ERR_CODE(FILE_ERR_NOEXIST); + } + + std::string line; + int result = SUCCESS; + + // 包含DataType字段,检查数据类型 + EVzResultDataType eDataType = keResultDataType_Invalid; + result = _GetLaserType(fileName, eDataType); + ERR_CODE_RETURN(result); + + LOG_INFO("Laser data type: %d \n", eDataType); + + SVzLaserLineData sLaserData; + memset(&sLaserData, 0, sizeof(SVzLaserLineData)); + + bool bFindLineNum = true; + int nLaserPointIdx = 0; + + while (std::getline(inputFile, line)) { + if (line.find("LineNum:") == 0) { + sscanf(line.c_str(), "LineNum:%d", &lineNum); + } else if (line.find("DataType:") == 0) { + + } else if (line.find("Line_") == 0) { + + if(false == bFindLineNum) { + laserLines.push_back(std::make_pair(eDataType, sLaserData)); + } + + int lineIndex; + unsigned int timeStamp; + int ptNum = 0; + sscanf(line.c_str(), "Line_%d_%u_%d", &lineIndex, &timeStamp, &ptNum); + + sLaserData.llFrameIdx = lineIndex; + sLaserData.llTimeStamp = timeStamp; + sLaserData.nPointCount = ptNum; + if (eDataType == keResultDataType_PointXYZRGBA) { + sLaserData.p3DPoint = new SVzNLPointXYZRGBA[ptNum]; + sLaserData.p2DPoint = new SVzNL2DLRPoint[ptNum]; + memset(sLaserData.p3DPoint, 0, sizeof(SVzNLPointXYZRGBA) * ptNum); + memset(sLaserData.p2DPoint, 0, sizeof(SVzNL2DLRPoint) * ptNum); + } else if(eDataType == keResultDataType_Position) { + sLaserData.p3DPoint = new SVzNL3DPosition[ptNum]; + sLaserData.p2DPoint = new SVzNL2DPosition[ptNum]; + memset(sLaserData.p3DPoint, 0, sizeof(SVzNL3DPosition) * ptNum); + memset(sLaserData.p2DPoint, 0, sizeof(SVzNL2DPosition) * ptNum); + } + nLaserPointIdx = 0; + bFindLineNum = false; + + } else if (line.find("{") == 0) { + // 使用正则表达式判断是XYZ还是RGBD格式 + // XYZ格式: {x,y,z}-{leftX,leftY}-{rightX,rightY} + // RGBD格式: {x,y,z,r,g,b}-{leftX,leftY}-{rightX,rightY} + + // 更精确的正则表达式,匹配完整的行格式 + if(sLaserData.p3DPoint == nullptr || sLaserData.p2DPoint == nullptr) { + LOG_ERROR("sLaserData.p3DPoint == nullptr || sLaserData.p2DPoint == nullptr \n"); + return ERR_CODE(DATA_ERR_INVALID); + } + + if (eDataType == keResultDataType_PointXYZRGBA) { + SVzNLPointXYZRGBA* pRGBAPoints = static_cast(sLaserData.p3DPoint); + SVzNL2DLRPoint* p2DPoints = static_cast(sLaserData.p2DPoint); + _ParseLaserScanPoint(line, pRGBAPoints[nLaserPointIdx], p2DPoints[nLaserPointIdx]); + nLaserPointIdx++; + } else { + SVzNL3DPosition* p3DPoints = static_cast(sLaserData.p3DPoint); + SVzNL2DPosition* p2DPoints = static_cast(sLaserData.p2DPoint); + _ParseLaserScanPoint(line, p3DPoints[nLaserPointIdx], p2DPoints[nLaserPointIdx]); + nLaserPointIdx++; + } + } + } + + // 添加最后一条扫描线数据 + if (!bFindLineNum) { + laserLines.push_back(std::make_pair(eDataType, sLaserData)); + } + + inputFile.close(); + LOG_INFO("Successfully loaded %d laser scan lines from file: %s\n", lineNum, fileName.c_str()); + return SUCCESS; +} + +// 保存激光扫描数据到文件 - 统一接口,支持两种类型的数据 +int LaserDataLoader::SaveLaserScanData(const std::string& fileName, + const std::vector>& laserLines, + int lineNum, + float scanSpeed, + int maxTimeStamp, + int clockPerSecond) +{ + LOG_INFO("Saving unified laser scan data to file: %s\n", fileName.c_str()); + + if (laserLines.empty() || lineNum <= 0) { + m_lastError = "Invalid input parameters for saving unified data"; + LOG_ERROR("Invalid parameters for saving unified laser data\n"); + return ERR_CODE(DEV_ARG_INVAILD); + } + + try { + std::ofstream sw(fileName); + if (!sw.is_open()) { + m_lastError = "Cannot open file for writing: " + fileName; + LOG_ERROR("Cannot open file for writing: %s\n", fileName.c_str()); + return ERR_CODE(FILE_ERR_WRITE); + } + + // 写入文件头 + sw << "LineNum:" << lineNum << std::endl; + sw << "DataType: 0" << std::endl; + sw << "ScanSpeed:" << scanSpeed << std::endl; + sw << "PointAdjust: 1" << std::endl; + sw << "MaxTimeStamp:" << maxTimeStamp << "_" << clockPerSecond << std::endl; + + // 写入每条扫描线数据 + for (const auto& linePair : laserLines) { + + EVzResultDataType dataType = linePair.first; + const SVzLaserLineData& lineData = linePair.second; + + sw << "Line_" << lineData.llFrameIdx << "_" << lineData.llTimeStamp << "_" << lineData.nPointCount << std::endl; + + // 根据数据类型写入点云数据 + if (dataType == keResultDataType_Position && lineData.p3DPoint) { + // 写入XYZ格式数据 + const SVzNL3DPosition* points = static_cast(lineData.p3DPoint); + const SVzNL2DPosition* points2D = static_cast(lineData.p2DPoint); + for (int i = 0; i < lineData.nPointCount; i++) { + float x = static_cast(points[i].pt3D.x); + float y = static_cast(points[i].pt3D.y); + float z = static_cast(points[i].pt3D.z); + sw << "{ " << x << "," << y << "," << z << " }-"; + sw << "{ " << points2D[i].ptLeft2D.x << "," << points2D[i].ptLeft2D.y << " }-"; + sw << "{ " << points2D[i].ptRight2D.x << "," << points2D[i].ptRight2D.y << " }" << std::endl; + } + } else if (dataType == keResultDataType_PointXYZRGBA && lineData.p3DPoint) { + // 写入RGBD格式数据 + const SVzNLPointXYZRGBA* points = static_cast(lineData.p3DPoint); + const SVzNL2DLRPoint* points2D = static_cast(lineData.p2DPoint); + for (int i = 0; i < lineData.nPointCount; i++) { + float x = static_cast(points[i].x); + float y = static_cast(points[i].y); + float z = static_cast(points[i].z); + int r = (points[i].nRGB >> 16) & 0xFF; + int g = (points[i].nRGB >> 8) & 0xFF; + int b = points[i].nRGB & 0xFF; + + sw << "{" << std::fixed << std::setprecision(6) << x << "," + << std::fixed << std::setprecision(6) << y << "," + << std::fixed << std::setprecision(6) << z << "," + << std::fixed << std::setprecision(6) << b * 1.0f / 255 << "," + << std::fixed << std::setprecision(6) << g * 1.0f / 255 << "," + << std::fixed << std::setprecision(6) << r * 1.0f / 255 << "}-"; + sw << "{ " << points2D[i].sLeft.x << "," << points2D[i].sLeft.y << " }-"; + sw << "{ " << points2D[i].sRight.x << "," << points2D[i].sRight.y << " }" << std::endl; + } + } + } + + sw.close(); + return SUCCESS; + + } catch (const std::exception& e) { + m_lastError = "Error saving unified file: " + std::string(e.what()); + LOG_ERROR("Error saving unified laser data to file: %s\n", e.what()); + return ERR_CODE(FILE_ERR_WRITE); + } +} + +void LaserDataLoader::FreeLaserScanData(std::vector>& laserLines) +{ + LOG_DEBUG("Freeing unified laser scan data, line count: %zu\n", laserLines.size()); + + if (!laserLines.empty()) { + for (auto& linePair : laserLines) { + EVzResultDataType dataType = linePair.first; + SVzLaserLineData& lineData = linePair.second; + + if (lineData.p3DPoint) { + delete[] lineData.p3DPoint; + lineData.p3DPoint = nullptr; + } + + if (lineData.p2DPoint) { + delete[] lineData.p2DPoint; + lineData.p2DPoint = nullptr; + } + } + laserLines.clear(); + } + + LOG_DEBUG("Unified laser scan data freed successfully\n"); +} + +// 转换统一格式数据为SVzNL3DLaserLine格式 +int LaserDataLoader::ConvertToSVzNL3DLaserLine(const std::vector>& unifiedData, + std::vector& xyzData) +{ + xyzData.clear(); + + for (const auto& linePair : unifiedData) { + EVzResultDataType dataType = linePair.first; + const SVzLaserLineData& lineData = linePair.second; + + // 只处理Position类型的数据 + if (dataType == keResultDataType_Position && lineData.p3DPoint) { + SVzNL3DLaserLine xyzLine; + xyzLine.nTimeStamp = lineData.llTimeStamp; + xyzLine.nPositionCnt = lineData.nPointCount; + + if (lineData.nPointCount > 0) { + xyzLine.p3DPosition = new SVzNL3DPosition[lineData.nPointCount]; + if (xyzLine.p3DPosition) { + memcpy(xyzLine.p3DPosition, lineData.p3DPoint, sizeof(SVzNL3DPosition) * lineData.nPointCount); + } else { + m_lastError = "Memory allocation failed for SVzNL3DPosition"; + LOG_ERROR("Memory allocation failed for SVzNL3DPosition\n"); + return ERR_CODE(DATA_ERR_INVALID); + } + } else { + xyzLine.p3DPosition = nullptr; + } + + xyzData.push_back(xyzLine); + } + } + + LOG_DEBUG("Converted %zu lines to SVzNL3DLaserLine format\n", xyzData.size()); + return SUCCESS; +} + +// 转换统一格式数据为SVzNLXYZRGBDLaserLine格式 +int LaserDataLoader::ConvertToSVzNLXYZRGBDLaserLine(const std::vector>& unifiedData, + std::vector& rgbdData) +{ + LOG_DEBUG("Converting unified data to SVzNLXYZRGBDLaserLine format\n"); + + rgbdData.clear(); + + for (const auto& linePair : unifiedData) { + EVzResultDataType dataType = linePair.first; + const SVzLaserLineData& lineData = linePair.second; + + // 只处理PointXYZRGBA类型的数据 + if (dataType == keResultDataType_PointXYZRGBA && lineData.p3DPoint) { + SVzNLXYZRGBDLaserLine rgbdLine; + rgbdLine.nTimeStamp = lineData.llTimeStamp; + rgbdLine.nPointCnt = lineData.nPointCount; + + if (lineData.nPointCount > 0) { + rgbdLine.p3DPoint = new SVzNLPointXYZRGBA[lineData.nPointCount]; + if (rgbdLine.p3DPoint) { + memcpy(rgbdLine.p3DPoint, lineData.p3DPoint, sizeof(SVzNLPointXYZRGBA) * lineData.nPointCount); + } else { + m_lastError = "Memory allocation failed for SVzNLPointXYZRGBA"; + LOG_ERROR("Memory allocation failed for SVzNLPointXYZRGBA\n"); + return ERR_CODE(DATA_ERR_INVALID); + } + } else { + rgbdLine.p3DPoint = nullptr; + } + + rgbdData.push_back(rgbdLine); + } + } + + LOG_DEBUG("Converted %zu lines to SVzNLXYZRGBDLaserLine format\n", rgbdData.size()); + return SUCCESS; +} + +// 释放转换后的SVzNL3DLaserLine数据内存 +void LaserDataLoader::FreeConvertedData(std::vector& xyzData) +{ + LOG_DEBUG("Freeing converted SVzNL3DLaserLine data, line count: %zu\n", xyzData.size()); + + if (!xyzData.empty()) { + for (auto& scanLine : xyzData) { + if (scanLine.p3DPosition) { + delete[] scanLine.p3DPosition; + scanLine.p3DPosition = nullptr; + } + } + xyzData.clear(); + } + + LOG_DEBUG("Converted SVzNL3DLaserLine data freed successfully\n"); +} + +// 释放转换后的SVzNLXYZRGBDLaserLine数据内存 +void LaserDataLoader::FreeConvertedData(std::vector& rgbdData) +{ + LOG_DEBUG("Freeing converted SVzNLXYZRGBDLaserLine data, line count: %zu\n", rgbdData.size()); + + if (!rgbdData.empty()) { + for (auto& scanLine : rgbdData) { + if (scanLine.p3DPoint) { + delete[] scanLine.p3DPoint; + scanLine.p3DPoint = nullptr; + } + } + rgbdData.clear(); + } + + LOG_DEBUG("Converted SVzNLXYZRGBDLaserLine data freed successfully\n"); +} + + +int LaserDataLoader::_ParseLaserScanPoint(const std::string& data, SVzNL3DPosition& sData, SVzNL2DPosition& s2DData) +{ + float X, Y, Z; + float leftX, leftY; + float rightX, rightY; + sscanf(data.c_str(), "{%f,%f,%f}-{%f,%f}-{%f,%f}", &X, &Y, &Z, &leftX, &leftY, &rightX, &rightY); + sData.pt3D.x = X; + sData.pt3D.y = Y; + sData.pt3D.z = Z; + s2DData.ptLeft2D.x = leftX; + s2DData.ptLeft2D.y = leftY; + s2DData.ptRight2D.x = rightX; + s2DData.ptRight2D.y = rightY; + return SUCCESS; +} + +int LaserDataLoader::_ParseLaserScanPoint(const std::string& data, SVzNLPointXYZRGBA& sData, SVzNL2DLRPoint& s2DData) +{ + float X, Y, Z; + float r, g, b; + float leftX, leftY; + float rightX, rightY; + sscanf(data.c_str(), "{%f,%f,%f,%f,%f,%f}-{%f,%f}-{%f,%f}", &X, &Y, &Z, &r, &g, &b, &leftX, &leftY, &rightX, &rightY); + sData.x = X; + sData.y = Y; + sData.z = Z; + + int nr = (int)(r * 255); + int ng = (int)(g * 255); + int nb = (int)(b * 255); + nb <<= 8; + nb += ng; + nb <<= 8; + nb += nr; + sData.nRGB = nb; + + s2DData.sLeft.x = leftX; + s2DData.sLeft.y = leftY; + s2DData.sRight.x = rightX; + s2DData.sRight.y = rightY; + return SUCCESS; +} + +// 获取激光数据类型 +int LaserDataLoader::_GetLaserType(const std::string& fileName, EVzResultDataType& eDataType) +{ + std::ifstream inputFile(fileName); + std::string linedata; + + if (!inputFile.is_open()) { + m_lastError = "Cannot open file: " + fileName; + return ERR_CODE(FILE_ERR_NOEXIST); + } + + bool bFind = false; + while (std::getline(inputFile, linedata)) { + if (linedata.find("{") == 0) { + // 修复正则表达式以匹配实际数据格式 + // XYZ格式: {x,y,z}-{leftX,leftY}-{rightX,rightY} + // RGBD格式: {x,y,z,r,g,b}-{leftX,leftY}-{rightX,rightY} + // 更宽松的正则表达式,允许更多的空格变化 + std::regex xyzPattern(R"(\{\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*,\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*,\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*\}\s*-\s*\{\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*,\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*\}\s*-\s*\{\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*,\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*\})"); + std::regex rgbdPattern(R"(\{\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*,\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*,\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*,\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*,\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*,\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*\}\s*-\s*\{\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*,\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*\}\s*-\s*\{\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*,\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*\})"); + + // 先尝试匹配RGBD格式(6个数字) + if (std::regex_match(linedata, rgbdPattern)) { + eDataType = keResultDataType_PointXYZRGBA; + bFind = true; + } + // 再尝试匹配XYZ格式(3个数字) + else if (std::regex_match(linedata, xyzPattern)) { + eDataType = keResultDataType_Position; + bFind = true; + } + break; + } + } + + inputFile.close(); + return bFind ? SUCCESS : ERR_CODE(FILE_ERR_FORMAT); +} diff --git a/App/LapWeld/LapWeldApp/Utils/Src/PathManager.cpp b/App/LapWeld/LapWeldApp/Utils/Src/PathManager.cpp new file mode 100644 index 0000000..090d2c5 --- /dev/null +++ b/App/LapWeld/LapWeldApp/Utils/Src/PathManager.cpp @@ -0,0 +1,66 @@ +#include "PathManager.h" +#include +#include +#include +#include +#include +#include "VrLog.h" + +QString PathManager::GetConfigFilePath() +{ + // 确保目标目录存在 + EnsureConfigDirectoryExists(); + return GetAppConfigDirectory() + "/config.xml"; +} + +QString PathManager::GetCalibrationFilePath() +{ + // 确保目标目录存在 + EnsureConfigDirectoryExists(); + return GetAppConfigDirectory() + "/EyeHandCalibMatrixInfo.ini"; +} + +QString PathManager::GetAppConfigDirectory() +{ + QString configDir = ""; +#ifdef _WIN32 + // Windows系统:使用程序目录 + configDir = GetProgramDirectory(); +#else + // Linux系统使用用户配置目录 + configDir = QDir::homePath() + "/.config/LapWeldApp/Config"; +#endif + return configDir + "/../LapWeldApp/Config"; +} + +bool PathManager::EnsureConfigDirectoryExists() +{ + QString configDir = GetAppConfigDirectory(); + + if (QDir().exists(configDir)) { + return true; + } + + LOG_INFO("Creating configuration directory: %s\n", configDir.toStdString().c_str()); + + bool success = QDir().mkpath(configDir); + if (success) { + LOG_INFO("Configuration directory created successfully\n"); + } else { + LOG_ERROR("Failed to create configuration directory: %s\n", configDir.toStdString().c_str()); + } + + return success; +} + + +QString PathManager::GetProgramDirectory() +{ + QString exePath = QCoreApplication::applicationFilePath(); + return QFileInfo(exePath).absoluteDir().path(); +} + +QString PathManager::GetUserConfigDirectory() +{ + return QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); +} \ No newline at end of file diff --git a/App/LapWeld/LapWeldApp/Utils/Src/PointCloudImageUtils.cpp b/App/LapWeld/LapWeldApp/Utils/Src/PointCloudImageUtils.cpp new file mode 100644 index 0000000..0adc2c5 --- /dev/null +++ b/App/LapWeld/LapWeldApp/Utils/Src/PointCloudImageUtils.cpp @@ -0,0 +1,517 @@ +#include "PointCloudImageUtils.h" +#include +#include +#include +#include +#include "VrLog.h" + +#ifndef PI +#define PI 3.14159265358979323846 +#endif + +QImage PointCloudImageUtils::GeneratePointCloudImage(SVzNL3DLaserLine* scanData, + int lineNum, + const std::vector& objOps) +{ + if (!scanData || lineNum <= 0) { + return QImage(); // 返回空图像 + } + + // 统计X和Y的范围 + double xMin = 0.0, xMax = -1.0; + double yMin = 0.0, yMax = -1.0; + CalculatePointCloudRange(scanData, lineNum, xMin, xMax, yMin, yMax); + + // 检查范围是否有效 + if (xMax < xMin || yMax < yMin) { + return QImage(); // 返回空图像 + } + + // 创建图像 + int imgRows = 992; + int imgCols = 1056; + int x_skip = 16; + int y_skip = 16; + + // 计算投影比例 + double y_rows = (double)(imgRows - y_skip *2); + double x_cols = (double)(imgCols - x_skip *2); + //计算投影比例 + double x_scale = (xMax - xMin) / x_cols; + double y_scale = (yMax - yMin) / y_rows; + if (x_scale < y_scale) + x_scale = y_scale; + else + y_scale = x_scale; + + QImage image(imgCols, imgRows, QImage::Format_RGB888); + image.fill(Qt::black); + QPainter painter(&image); + + // 绘制点云 + for (int line = 0; line < lineNum; line++) { + for (int i = 0; i < scanData[line].nPositionCnt; i++) { + SVzNL3DPosition* pt3D = &scanData[line].p3DPosition[i]; + if (pt3D->pt3D.z < 1e-4) continue; + + // 解析点索引信息 + int vType = pt3D->nPointIdx & 0xff; + int hType = vType >> 4; + int objId = (pt3D->nPointIdx >> 16) & 0xff; + vType = vType & 0x0f; + + // 根据线特征类型确定颜色和大小 + QColor pointColor; + int pointSize = 1; + GetLineFeatureStyle(vType, hType, objId, pointColor, pointSize); + + int px = (int)((pt3D->pt3D.x - xMin) / x_scale + x_skip); + int py = (int)((pt3D->pt3D.y - yMin) / y_scale + y_skip); + + if (px >= 0 && px < imgCols && py >= 0 && py < imgRows) { + painter.setPen(QPen(pointColor, 1)); + painter.drawPoint(px, py); + } + } + } + + // 绘制检测目标和方向线 + DrawDetectionTargets(painter, objOps, xMin, x_scale, x_skip, yMin, y_scale, y_skip, imgCols, imgRows); + + return image; +} + +QImage PointCloudImageUtils::GeneratePointCloudImage(SVzNLXYZRGBDLaserLine* scanData, + int lineNum, + const std::vector& objOps) +{ + + int imgRows = 992; + int imgCols = 1056; + QImage image(imgCols, imgRows, QImage::Format_RGB888); + image.fill(Qt::black); + + + if (!scanData || lineNum <= 0) { + return image; // 返回空图像 + } + + // 统计X和Y的范围 - 参考_RGBDto2DImage函数 + double xMin = 0.0, xMax = -1.0; + double yMin = 0.0, yMax = -1.0; + + for (int line = 0; line < lineNum; line++) { + for (int i = 0; i < scanData[line].nPointCnt; i++) { + SVzNLPointXYZRGBA* pt3D = &scanData[line].p3DPoint[i]; + if (pt3D->z < 1e-4) continue; + + // 更新X范围 + if (xMax < xMin) { + xMin = xMax = pt3D->x; + } else { + if (xMin > pt3D->x) xMin = pt3D->x; + if (xMax < pt3D->x) xMax = pt3D->x; + } + + // 更新Y范围 + if (yMax < yMin) { + yMin = yMax = pt3D->y; + } else { + if (yMin > pt3D->y) yMin = pt3D->y; + if (yMax < pt3D->y) yMax = pt3D->y; + } + } + } + + // 检查范围是否有效 + if (xMax < xMin || yMax < yMin) { + return image; // 返回空图像 + } + + // 创建图像 - 参考_RGBDto2DImage函数的尺寸和偏移 + int x_skip = 16; + int y_skip = 16; + double y_rows = (double)(imgRows - y_skip * 2); + double x_cols = (double)(imgCols - x_skip * 2); + + // 计算投影比例 - 参考_RGBDto2DImage函数的比例计算 + double x_scale = (xMax - xMin) / x_cols; + double y_scale = (yMax - yMin) / y_rows; + if (x_scale < y_scale) + x_scale = y_scale; + else + y_scale = x_scale; + + QPainter painter(&image); + + // 绘制点云 - 参考_RGBDto2DImage函数的绘制方式 + for (int line = 0; line < lineNum; line++) { + for (int i = 0; i < scanData[line].nPointCnt; i++) { + SVzNLPointXYZRGBA* pt3D = &scanData[line].p3DPoint[i]; + if (pt3D->z < 1e-4) continue; + + // 解析RGB颜色 - 参考_RGBDto2DImage函数的解析方式 + int nRGB = pt3D->nRGB; + int r = nRGB & 0xff; + nRGB >>= 8; + int g = nRGB & 0xff; + nRGB >>= 8; + int b = nRGB & 0xff; + + QColor pointColor(r, g, b); + + int px = (int)((pt3D->x - xMin) / x_scale + x_skip); + int py = (int)((pt3D->y - yMin) / y_scale + y_skip); + + if (px >= 0 && px < imgCols && py >= 0 && py < imgRows) { + painter.setPen(QPen(pointColor, 1)); + painter.drawPoint(px, py); + } + } + } + + // 绘制检测目标和方向线 + DrawDetectionTargets(painter, objOps, xMin, x_scale, x_skip, yMin, y_scale, y_skip, imgCols, imgRows); + + return image; +} + +// 新的点云图像生成函数 - 基于X、Y范围创建图像 +QImage PointCloudImageUtils::GeneratePointCloudImage(SVzNLXYZRGBDLaserLine* scanData, int lineNum) +{ + if (!scanData || lineNum <= 0) { + return QImage(); + } + + // 1. 遍历X, Y的范围,计算最小和最大值 + double xMin = std::numeric_limits::max(); + double xMax = std::numeric_limits::lowest(); + double yMin = std::numeric_limits::max(); + double yMax = std::numeric_limits::lowest(); + + for (int i = 0; i < lineNum; i++) { + if (scanData[i].p3DPoint && scanData[i].nPointCnt > 0) { + for (int j = 0; j < scanData[i].nPointCnt; j++) { + const SVzNLPointXYZRGBA& point = scanData[i].p3DPoint[j]; + + // 更新X范围 + if (point.x < xMin) xMin = point.x; + if (point.x > xMax) xMax = point.x; + + // 更新Y范围 + if (point.y < yMin) yMin = point.y; + if (point.y > yMax) yMax = point.y; + } + } + } + + // 检查范围是否有效 + if (xMin >= xMax || yMin >= yMax) { + return QImage(); + } + + // 2. 根据范围创建图像大小 + // 设置图像分辨率,可以根据需要调整 + const int imageWidth = xMax - xMin; + const int imageHeight = yMax - yMin; + + + // 3. 创建默认黑底图像 + QImage image(imageWidth, imageHeight, QImage::Format_RGB888); + image.fill(Qt::black); + + // 4. 遍历点云数据,将3D X, Y对应的颜色值赋值给图像 + for (int i = 0; i < lineNum; i++) { + if (scanData[i].p3DPoint && scanData[i].nPointCnt > 0) { + for (int j = 0; j < scanData[i].nPointCnt; j++) { + const SVzNLPointXYZRGBA& point = scanData[i].p3DPoint[j]; + + // 将3D坐标转换为图像坐标 + int imageX = static_cast((point.x - xMin)); + int imageY = static_cast((point.y - yMin)); + + // 确保坐标在图像范围内 + if (imageX >= 0 && imageX < imageWidth && + imageY >= 0 && imageY < imageHeight) { + + // 解析RGB颜色 - 参考_RGBDto2DImage函数的解析方式 + int nRGB = point.nRGB; + int r = nRGB & 0xff; + nRGB >>= 8; + int g = nRGB & 0xff; + nRGB >>= 8; + int b = nRGB & 0xff; + + QColor pointColor(r, g, b); + // 设置图像像素颜色 + image.setPixel(imageX, imageY, pointColor.rgb()); + } + } + } + } + + return image; +} + +void PointCloudImageUtils::GetLineFeatureStyle(int vType, int hType, int objId, + QColor& pointColor, int& pointSize) +{ + pointSize = 1; + + // 优先根据垂直方向特征设置颜色 + if (LINE_FEATURE_L_JUMP_H2L == vType) { + pointColor = QColor(255, 97, 0); // 橙色 + pointSize = 2; + } + else if (LINE_FEATURE_L_JUMP_L2H == vType) { + pointColor = QColor(255, 255, 0); // 黄色 + pointSize = 2; + } + else if (LINE_FEATURE_V_SLOPE == vType) { + pointColor = QColor(255, 0, 255); // 紫色 + pointSize = 2; + } + else if (LINE_FEATURE_L_SLOPE_H2L == vType) { + pointColor = QColor(160, 82, 45); // 褐色 + pointSize = 2; + } + else if ((LINE_FEATURE_LINE_ENDING_0 == vType) || (LINE_FEATURE_LINE_ENDING_1 == vType)) { + pointColor = QColor(255, 0, 0); // 红色 + pointSize = 2; + } + else if (LINE_FEATURE_L_SLOPE_L2H == vType) { + pointColor = QColor(233, 150, 122); // 浅褐色 + pointSize = 2; + } + // 检查水平方向特征 + else if (LINE_FEATURE_L_JUMP_H2L == hType) { + pointColor = QColor(0, 0, 255); // 蓝色 + pointSize = 2; + } + else if (LINE_FEATURE_L_JUMP_L2H == hType) { + pointColor = QColor(0, 255, 255); // 青色 + pointSize = 2; + } + else if (LINE_FEATURE_V_SLOPE == hType) { + pointColor = QColor(0, 255, 0); // 绿色 + pointSize = 2; + } + else if (LINE_FEATURE_L_SLOPE_H2L == hType) { + pointColor = QColor(85, 107, 47); // 橄榄绿 + pointSize = 2; + } + else if (LINE_FEATURE_L_SLOPE_L2H == hType) { + pointColor = QColor(0, 255, 154); // 浅绿色 + pointSize = 2; + } + else if ((LINE_FEATURE_LINE_ENDING_0 == hType) || (LINE_FEATURE_LINE_ENDING_1 == hType)) { + pointColor = QColor(255, 0, 0); // 红色 + pointSize = 3; + } + // 检查是否为目标对象 + else if (objId > 0) { + pointColor = GetObjectColor(objId); + pointSize = 1; + } + // 默认颜色 + else { + pointColor = QColor(150, 150, 150); // 深灰色 + pointSize = 1; + } +} + +QColor PointCloudImageUtils::GetObjectColor(int index) +{ + QColor objColors[8] = { + QColor(245,222,179), QColor(210,105,30), QColor(240,230,140), QColor(135,206,235), + QColor(250,235,215), QColor(189,252,201), QColor(221,160,221), QColor(188,143,143) + }; + + return objColors[index % 8]; +} + +void PointCloudImageUtils::CalculatePointCloudRange(SVzNL3DLaserLine* scanData, int lineNum, + double& xMin, double& xMax, + double& yMin, double& yMax) +{ + xMin = 0.0; xMax = -1.0; + yMin = 0.0; yMax = -1.0; + + for (int line = 0; line < lineNum; line++) { + for (int i = 0; i < scanData[line].nPositionCnt; i++) { + SVzNL3DPosition* pt3D = &scanData[line].p3DPosition[i]; + if (pt3D->pt3D.z < 1e-4) continue; + + // 更新X范围 + if (xMax < xMin) { + xMin = xMax = pt3D->pt3D.x; + } else { + if (xMin > pt3D->pt3D.x) xMin = pt3D->pt3D.x; + if (xMax < pt3D->pt3D.x) xMax = pt3D->pt3D.x; + } + + // 更新Y范围 + if (yMax < yMin) { + yMin = yMax = pt3D->pt3D.y; + } else { + if (yMin > pt3D->pt3D.y) yMin = pt3D->pt3D.y; + if (yMax < pt3D->pt3D.y) yMax = pt3D->pt3D.y; + } + } + } +} + +void PointCloudImageUtils::DrawDetectionTargets(QPainter& painter, + const std::vector& objOps, + double xMin, double xScale, int xSkip, + double yMin, double yScale, int ySkip, + int imgCols, int imgRows) +{ + // 绘制检测目标和方向线 + for (size_t i = 0; i < objOps.size(); i++) { + QColor objColor = (i == 0) ? QColor(255, 0, 0) : QColor(255, 255, 0); + int size = (i == 0) ? 12 : 8; + + int px = (int)((objOps[i].centerPos.x - xMin) / xScale + xSkip); + int py = (int)((objOps[i].centerPos.y - yMin) / yScale + ySkip); + + if (px >= 0 && px < imgCols && py >= 0 && py < imgRows) { + // 绘制抓取点圆圈 + painter.setPen(QPen(objColor, 2)); + painter.setBrush(QBrush(objColor)); + painter.drawEllipse(px - size/2, py - size/2, size, size); + + // 绘制方向线 + const double deg2rad = PI / 180.0; + + // 使用检测目标实际2D尺寸的较大值的一半作为方向线长度 + double maxSize = std::max(objOps[i].objSize.dHeight, objOps[i].objSize.dWidth); + double R = std::max(20.0, maxSize / 6.0); + + const double yaw = objOps[i].centerPos.z_yaw * deg2rad; + double cy = cos(yaw); + double sy = sin(yaw); + double x1 = objOps[i].centerPos.x + R * cy; + double y1 = objOps[i].centerPos.y - R * sy; + double x2 = objOps[i].centerPos.x - R * cy; + double y2 = objOps[i].centerPos.y + R * sy; + + int px1 = (int)((x1 - xMin) / xScale + xSkip); + int py1 = (int)((y1 - yMin) / yScale + ySkip); + int px2 = (int)((x2 - xMin) / xScale + xSkip); + int py2 = (int)((y2 - yMin) / yScale + ySkip); + + // 绘制方向线 + painter.setPen(QPen(objColor, 3)); + painter.drawLine(px1, py1, px2, py2); + + // 绘制目标编号 + painter.setPen(QPen(Qt::white, 1)); + painter.setFont(QFont("Arial", 15, QFont::Bold)); + painter.drawText(px + 15, py - 10, QString("%1").arg(i + 1)); + } + } +} + + +void PointCloudImageUtils::DrawDetectionTargets(QPainter& painter, + const std::vector& objOps, + double xMin, double xScale, int xSkip, + double yMin, double yScale, int ySkip, + int imgCols, int imgRows) +{ + // 绘制检测目标和方向线 - 参考bagPositioning_test.cpp中的_XOYprojection_RGBD函数 + for (size_t i = 0; i < objOps.size(); i++) { + QColor objColor = (i == 0) ? QColor(255, 0, 0) : QColor(255, 255, 0); + int size = (i == 0) ? 20 : 10; + + int px = (int)((objOps[i].centerPos.x - xMin) / xScale + xSkip); + int py = (int)((objOps[i].centerPos.y - yMin) / yScale + ySkip); + + if (px >= 0 && px < imgCols && py >= 0 && py < imgRows) { + // 绘制抓取点圆圈 + painter.setPen(QPen(objColor, 2)); + painter.setBrush(QBrush(objColor)); + painter.drawEllipse(px - size/2, py - size/2, size, size); + + // 绘制方向线 - 参考bagPositioning_test.cpp中的实现 + const double deg2rad = PI / 180.0; + + // 使用检测目标实际2D尺寸的较大值的一半作为方向线长度 + double maxSize = std::max(objOps[i].objSize.dHeight, objOps[i].objSize.dWidth); + double R = std::max(20.0, maxSize / 6.0); + + const double yaw = objOps[i].centerPos.z_yaw * deg2rad; + double cy = cos(yaw); + double sy = sin(yaw); + + // 计算方向线的端点 + double x1 = objOps[i].centerPos.x + R * cy; + double y1 = objOps[i].centerPos.y - R * sy; + double x2 = objOps[i].centerPos.x - R * cy; + double y2 = objOps[i].centerPos.y + R * sy; + + int px1 = (int)((x1 - xMin) / xScale + xSkip); + int py1 = (int)((y1 - yMin) / yScale + ySkip); + int px2 = (int)((x2 - xMin) / xScale + xSkip); + int py2 = (int)((y2 - yMin) / yScale + ySkip); + + // 绘制方向线 + painter.setPen(QPen(objColor, 2)); + painter.drawLine(px1, py1, px2, py2); + + // 根据orienFlag绘制箭头 + if (objOps[i].orienFlag == 1) { + // 绿色箭头 - 正面 + painter.setPen(QPen(QColor(0, 255, 0), 2)); + + // 计算箭头端点 + double arrowLen = R / 3; + double arrowAngle = 30 * deg2rad; + double ca = cos(arrowAngle); + double sa = sin(arrowAngle); + + double x3 = x1 - arrowLen * ca * cy + arrowLen * sa * sy; + double y3 = y1 + arrowLen * ca * sy + arrowLen * sa * cy; + double x4 = x1 - arrowLen * ca * cy - arrowLen * sa * sy; + double y4 = y1 + arrowLen * ca * sy - arrowLen * sa * cy; + + int px3 = (int)((x3 - xMin) / xScale + xSkip); + int py3 = (int)((y3 - yMin) / yScale + ySkip); + int px4 = (int)((x4 - xMin) / xScale + xSkip); + int py4 = (int)((y4 - yMin) / yScale + ySkip); + + painter.drawLine(px1, py1, px3, py3); + painter.drawLine(px1, py1, px4, py4); + } + else if (objOps[i].orienFlag == 2) { + // 蓝色箭头 - 反面 + painter.setPen(QPen(QColor(0, 0, 255), 2)); + + // 计算箭头端点 + double arrowLen = R / 3; + double arrowAngle = 30 * deg2rad; + double ca = cos(arrowAngle); + double sa = sin(arrowAngle); + + double x3 = x1 - arrowLen * ca * cy + arrowLen * sa * sy; + double y3 = y1 + arrowLen * ca * sy + arrowLen * sa * cy; + double x4 = x1 - arrowLen * ca * cy - arrowLen * sa * sy; + double y4 = y1 + arrowLen * ca * sy - arrowLen * sa * cy; + + int px3 = (int)((x3 - xMin) / xScale + xSkip); + int py3 = (int)((y3 - yMin) / yScale + ySkip); + int px4 = (int)((x4 - xMin) / xScale + xSkip); + int py4 = (int)((y4 - yMin) / yScale + ySkip); + + painter.drawLine(px1, py1, px3, py3); + painter.drawLine(px1, py1, px4, py4); + } + + // 绘制目标编号 + painter.setPen(QPen(Qt::white, 1)); + painter.setFont(QFont("Arial", 15, QFont::Bold)); + painter.drawText(px + 15, py - 10, QString("%1").arg(i + 1)); + } + } +} diff --git a/App/LapWeld/LapWeldApp/Version.h b/App/LapWeld/LapWeldApp/Version.h new file mode 100644 index 0000000..cecfee0 --- /dev/null +++ b/App/LapWeld/LapWeldApp/Version.h @@ -0,0 +1,23 @@ +#ifndef VERSION_H +#define VERSION_H + + +#define LAPWELD_VERSION_STRING "1.1.3" +#define LAPWELD_BUILD_STRING "1" +#define LAPWELD_FULL_VERSION_STRING "V1.1.3_1" + +// 获取版本信息的便捷函数 +inline const char* GetLapWeldVersion() { + return LAPWELD_VERSION_STRING; +} + +inline const char* GetLapWeldBuild() { + return LAPWELD_BUILD_STRING; +} + +inline const char* GetLapWeldFullVersion() { + return LAPWELD_FULL_VERSION_STRING; +} + +#endif // VERSION_H + diff --git a/App/LapWeld/LapWeldApp/devstatus.cpp b/App/LapWeld/LapWeldApp/devstatus.cpp new file mode 100644 index 0000000..3ddec6d --- /dev/null +++ b/App/LapWeld/LapWeldApp/devstatus.cpp @@ -0,0 +1,329 @@ +#include "devstatus.h" +#include "ui_devstatus.h" +#include +#include +#include "VrLog.h" + +devstatus::devstatus(QWidget *parent) + : QWidget(parent) + , ui(new Ui::devstatus) + , m_selectedCameraIndex(0) + , m_camera1Name("") + , m_camera2Name("") +{ + ui->setupUi(this); + + // 强制应用样式表 - 这是关键! + this->setAttribute(Qt::WA_StyledBackground, true); + + // 初始化时设置样式 + setItemStyle(); + + // 初始化点击事件 + initClickEvents(); + + // 初始化设备状态(离线状态) + updateCamera1Status(false); + updateCamera2Status(false); + updateRobotStatus(false); + + setCameraSelectedStyle(1); +} + +devstatus::~devstatus() +{ + delete ui; +} + +// 设置devstatus的样式 +void devstatus::setItemStyle() +{ + this->setStyleSheet( + "devstatus { " + " border: 2px solid #191A1C; " // 添加边框作为格子间分隔线 + " border-radius: 0px; " // 去掉圆角,让分隔线更清晰 + "} " + ); +} + +// 设置相机状态图片的私有成员函数 +void devstatus::setCameraStatusImage(QWidget* widget, bool isOnline) { + if (!widget) return; + + QString imagePath = isOnline ? ":/resource/camera_online.png" : ":/resource/camera_offline.png"; + + widget->setStyleSheet(QString("image: url(%1);").arg(imagePath)); + // widget->setFixedSize(32, 32); // 调整大小以适应图片 +} + +// 设置机械臂状态图片的私有成员函数 +void devstatus::setRobotStatusImage(QWidget* widget, bool isOnline) { + if (!widget) return; + + QString imagePath = isOnline ? ":/resource/robot_online.png" : ":/resource/robot_offline.png"; + + widget->setStyleSheet(QString("image: url(%1); ").arg(imagePath)); + // widget->setFixedSize(32, 32); // 调整大小以适应图片 +} + +// 设置相机1名称 +void devstatus::setCamera1Name(const QString& name) +{ + m_camera1Name = name; + // 如果相机1已经初始化过状态,则更新显示文本 + if (ui->dev_camera_1_txt) { + updateCamera1DisplayText(ui->dev_camera_1_txt->text().contains("在线")); + } +} + +// 设置相机2名称 +void devstatus::setCamera2Name(const QString& name) +{ + m_camera2Name = name; + // 如果相机2已经初始化过状态,则更新显示文本 + if (ui->dev_camera_2_txt) { + updateCamera2DisplayText(ui->dev_camera_2_txt->text().contains("在线")); + } +} + +// 更新相机1状态 +void devstatus::updateCamera1Status(bool isConnected) +{ + setCameraStatusImage(ui->dev_camer_1_img, isConnected); + updateCamera1DisplayText(isConnected); +} + +// 更新相机2状态 +void devstatus::updateCamera2Status(bool isConnected) +{ + setCameraStatusImage(ui->dev_camer_2_img, isConnected); + updateCamera2DisplayText(isConnected); +} + +// 更新相机1显示文本的私有方法 +void devstatus::updateCamera1DisplayText(bool isConnected) +{ + QString statusText; + if (m_camera1Name.isEmpty()) { + statusText = isConnected ? "相机1在线" : "相机1离线"; + } else { + statusText = isConnected ? QString("%1在线").arg(m_camera1Name) : QString("%1离线").arg(m_camera1Name); + } + + QString colorStyle = isConnected ? "color: rgb(140, 180, 60);" : "color: rgb(220, 60, 60);"; + + ui->dev_camera_1_txt->setText(statusText); + ui->dev_camera_1_txt->setStyleSheet(colorStyle); +} + +// 更新相机2显示文本的私有方法 +void devstatus::updateCamera2DisplayText(bool isConnected) +{ + QString statusText; + if (m_camera2Name.isEmpty()) { + statusText = isConnected ? "相机2在线" : "相机2离线"; + } else { + statusText = isConnected ? QString("%1在线").arg(m_camera2Name) : QString("%1离线").arg(m_camera2Name); + } + + QString colorStyle = isConnected ? "color: rgb(140, 180, 60);" : "color: rgb(220, 60, 60);"; + + ui->dev_camera_2_txt->setText(statusText); + ui->dev_camera_2_txt->setStyleSheet(colorStyle); +} + +// 更新机械臂状态 +void devstatus::updateRobotStatus(bool isConnected) +{ + setRobotStatusImage(ui->dev_robot_img, isConnected); + + QString statusText = isConnected ? "机械臂在线" : "机械臂离线"; + QString colorStyle = isConnected ? "color: rgb(140, 180, 60);" : "color: rgb(220, 60, 60);"; + + ui->dev_robot_txt->setText(statusText); + ui->dev_robot_txt->setStyleSheet(colorStyle); +} + +// 更新串口状态 +void devstatus::updateSerialStatus(bool isConnected) +{ + setRobotStatusImage(ui->dev_robot_img, isConnected); + QString statusText = isConnected ? "RS485在线" : "RS485离线"; + QString colorStyle = isConnected ? "color: rgb(140, 180, 60);" : "color: rgb(220, 60, 60);"; + + ui->dev_robot_txt->setText(statusText); + ui->dev_robot_txt->setStyleSheet(colorStyle); +} + +// 设置相机数量(用于控制相机二的显示和布局方向) +void devstatus::setCameraCount(int cameraCount) +{ + // 如果相机数量小于2,隐藏相机二相关的UI元素 + bool showCamera2 = (cameraCount >= 2); + + LOG_DEBUG("setCameraCount: %d \n", cameraCount); + + // 隐藏整个相机2的frame,而不是单独隐藏图片和文字 + if (ui->frame_camera_2) { + ui->frame_camera_2->setVisible(showCamera2); + } else { + // 如果frame_camera_2不存在,则单独隐藏图片和文字控件 + ui->dev_camer_2_img->setVisible(showCamera2); + ui->dev_camera_2_txt->setVisible(showCamera2); + } + + // 根据相机数量选择布局方式 + if (cameraCount >= 2) { + // 多相机:纵向排列 + setVerticalLayout(); + } else { + // 单相机:横向排列,相机和机械臂各占一半 + setHorizontalLayoutForSingleCamera(); + } +} + +// 设置为纵向布局 +void devstatus::setVerticalLayout() +{ + // 调整主widget的尺寸以适应纵向布局 + this->setMinimumSize(180, 198); + this->resize(180, 198); + + // 通过调整每个frame的尺寸和位置来模拟垂直布局 + adjustFramesForVerticalLayout(); + + LOG_DEBUG("Layout adjusted for vertical display\n"); +} + +// 设置为单相机横向布局(相机和机械臂各占一半) +void devstatus::setHorizontalLayoutForSingleCamera() +{ + // 设置单相机模式的widget尺寸 + this->setMinimumSize(180, 80); + this->resize(180, 80); + + // 调整为单相机横向布局的frame尺寸和位置 + adjustFramesForSingleCameraHorizontalLayout(); + + LOG_DEBUG("Layout adjusted for single camera horizontal display\n"); +} + +// 调整frames为纵向排列 +void devstatus::adjustFramesForVerticalLayout() +{ + int frameWidth = 180; + int frameHeight = 64; + int spacing = 2; + int startY = 0; + + // 重新定位frame的位置以实现纵向排列效果 + if (ui->frame_camera_1) { + ui->frame_camera_1->setGeometry(0, startY, frameWidth, frameHeight); + } + + if (ui->frame_camera_2) { + ui->frame_camera_2->setGeometry(0, startY + frameHeight + spacing, frameWidth, frameHeight); + startY += frameHeight + spacing; + } + + if (ui->frame_robot) { + ui->frame_robot->setGeometry(0, startY + frameHeight + spacing, frameWidth, frameHeight); + } +} + +// 调整frames为单相机横向排列(相机和机械臂各占一半) +void devstatus::adjustFramesForSingleCameraHorizontalLayout() +{ + int frameWidth = 275; // 每个frame占一半宽度 (180/2 - 5间距) + int frameHeight = 70; + int spacing = 6; + + // 相机1占左半边 + if (ui->frame_camera_1) { + ui->frame_camera_1->setGeometry(0, 0, frameWidth, frameHeight); + } + + // 相机2隐藏(在setCameraCount中已处理) + + // 机械臂占右半边 + if (ui->frame_robot) { + ui->frame_robot->setGeometry(frameWidth + spacing, 0, frameWidth, frameHeight); + } +} + +// 初始化点击事件 +void devstatus::initClickEvents() +{ + // 为相机1的frame添加点击事件 + if (ui->frame_camera_1) { + ui->frame_camera_1->installEventFilter(this); + ui->frame_camera_1->setMouseTracking(true); + ui->frame_camera_1->setCursor(Qt::PointingHandCursor); // 设置鼠标样式为手型 + } + + // 为相机2的frame添加点击事件 + if (ui->frame_camera_2) { + ui->frame_camera_2->installEventFilter(this); + ui->frame_camera_2->setMouseTracking(true); + ui->frame_camera_2->setCursor(Qt::PointingHandCursor); // 设置鼠标样式为手型 + } +} + +// 事件过滤器,处理鼠标点击事件 +bool devstatus::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::MouseButtonPress) { + QMouseEvent *mouseEvent = static_cast(event); + if (mouseEvent->button() == Qt::LeftButton) { + if (obj == ui->frame_camera_1) { + onCamera1Clicked(); + return true; + } else if (obj == ui->frame_camera_2) { + onCamera2Clicked(); + return true; + } + } + } + return QWidget::eventFilter(obj, event); +} + +// 相机1点击槽函数 +void devstatus::onCamera1Clicked() +{ + if (m_selectedCameraIndex != 1) { + m_selectedCameraIndex = 1; + setCameraSelectedStyle(1); + emit cameraClicked(1); + } +} + +// 相机2点击槽函数 +void devstatus::onCamera2Clicked() +{ + if (m_selectedCameraIndex != 2) { + m_selectedCameraIndex = 2; + setCameraSelectedStyle(2); + emit cameraClicked(2); + } +} + +// 设置相机选中状态的样式 +void devstatus::setCameraSelectedStyle(int cameraIndex) +{ + // 重置所有相机的样式 + if (ui->frame_camera_1) { + ui->frame_camera_1->setStyleSheet("background-color: rgb(37, 38, 42);"); + } + if (ui->frame_camera_2) { + ui->frame_camera_2->setStyleSheet("background-color: rgb(37, 38, 42);"); + } + + // 设置选中相机的样式 + QString selectedStyle = "QFrame { background-color: rgb(67, 68, 72);}"; + + if (cameraIndex == 1 && ui->frame_camera_1) { + ui->frame_camera_1->setStyleSheet(selectedStyle); + } else if (cameraIndex == 2 && ui->frame_camera_2) { + ui->frame_camera_2->setStyleSheet(selectedStyle); + } +} diff --git a/App/LapWeld/LapWeldApp/devstatus.h b/App/LapWeld/LapWeldApp/devstatus.h new file mode 100644 index 0000000..0633b8e --- /dev/null +++ b/App/LapWeld/LapWeldApp/devstatus.h @@ -0,0 +1,84 @@ +#ifndef DEVSTATUS_H +#define DEVSTATUS_H + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Ui { +class devstatus; +} + +class devstatus : public QWidget +{ + Q_OBJECT + +public: + explicit devstatus(QWidget *parent = nullptr); + ~devstatus(); + + // 设备状态更新方法 + void updateCamera1Status(bool isConnected); + void updateCamera2Status(bool isConnected); + void updateRobotStatus(bool isConnected); + void updateSerialStatus(bool isConnected); + + // 设置相机名称方法 + void setCamera1Name(const QString& name); + void setCamera2Name(const QString& name); + + // 设置相机数量(用于控制相机二的显示和布局方向) + void setCameraCount(int cameraCount); + + // 事件过滤器 + bool eventFilter(QObject *obj, QEvent *event) override; + +signals: + // 相机点击信号 + void cameraClicked(int cameraIndex); + +private slots: + // 相机点击槽函数 + void onCamera1Clicked(); + void onCamera2Clicked(); + +private: + Ui::devstatus *ui; + + // 当前选中的相机索引 + int m_selectedCameraIndex; + + // 相机名称 + QString m_camera1Name; + QString m_camera2Name; + + // 设置状态图片的私有成员函数 + void setCameraStatusImage(QWidget* widget, bool isOnline); + void setRobotStatusImage(QWidget* widget, bool isOnline); + + // 设置样式的私有成员函数 + void setItemStyle(); + + // 设置相机选中状态的样式 + void setCameraSelectedStyle(int cameraIndex); + + // 布局管理的私有成员函数 + void setVerticalLayout(); + void setHorizontalLayoutForSingleCamera(); + void adjustFramesForVerticalLayout(); + void adjustFramesForSingleCameraHorizontalLayout(); + + // 初始化点击事件 + void initClickEvents(); + + // 更新相机显示文本的私有方法 + void updateCamera1DisplayText(bool isConnected); + void updateCamera2DisplayText(bool isConnected); +}; + +#endif // DEVSTATUS_H diff --git a/App/LapWeld/LapWeldApp/devstatus.ui b/App/LapWeld/LapWeldApp/devstatus.ui new file mode 100644 index 0000000..a3de5dd --- /dev/null +++ b/App/LapWeld/LapWeldApp/devstatus.ui @@ -0,0 +1,181 @@ + + + devstatus + + + + 0 + 0 + 556 + 75 + + + + Form + + + background-color: rgb(37, 38, 42); + + + + + 371 + 1 + 179 + 64 + + + + background-color: rgb(37, 38, 42); + + + QFrame::Shape::StyledPanel + + + QFrame::Shadow::Raised + + + + + 80 + 20 + 90 + 26 + + + + + 12 + + + + color: rgb(0, 255, 0); + + + 机械臂在线 + + + + + + 20 + 10 + 48 + 48 + + + + image: url(:/resource/robot_offline.png); + + + + + + + 186 + 1 + 179 + 64 + + + + background-color: rgb(37, 38, 42); + + + QFrame::Shape::StyledPanel + + + QFrame::Shadow::Raised + + + + + 80 + 20 + 90 + 26 + + + + + 12 + + + + color: rgb(0, 255, 0); + + + 相机在线 + + + + + + 20 + 10 + 48 + 48 + + + + image: url(:/resource/camera_offline.png); + + + + + + + 1 + 1 + 179 + 64 + + + + background-color: rgb(37, 38, 42); + + + QFrame::Shape::StyledPanel + + + QFrame::Shadow::Raised + + + + + 80 + 20 + 90 + 26 + + + + + 12 + + + + color: rgb(0, 255, 0); + + + 相机在线 + + + + + + 20 + 10 + 48 + 48 + + + + image: url(:/resource/camera_offline.png); + + + + + + + diff --git a/App/LapWeld/LapWeldApp/dialogcamera.cpp b/App/LapWeld/LapWeldApp/dialogcamera.cpp new file mode 100644 index 0000000..fcd7e28 --- /dev/null +++ b/App/LapWeld/LapWeldApp/dialogcamera.cpp @@ -0,0 +1,199 @@ +#include "dialogcamera.h" +#include "ui_dialogcamera.h" +#include +#include "VrLog.h" + +DialogCamera::DialogCamera(const std::vector>& deviceList, + QWidget *parent) : + QDialog(parent), + ui(new Ui::DialogCamera), + m_deviceList(deviceList) +{ + ui->setupUi(this); + + // 隐藏标题栏 + setWindowFlags(Qt::FramelessWindowHint); + + // 检查设备列表是否有效 + if (m_deviceList.empty()) { + return; + } + + // 初始化相机选择下拉框 + InitCameraComboBox(); + + // 设置默认选择第一个相机 + m_currentCameraIndex = 0; + m_currentDevice = m_deviceList[0].second; + + // 连接ComboBox的信号槽 + connect(ui->combo_camera, QOverload::of(&QComboBox::currentIndexChanged), + this, &DialogCamera::on_camera_selection_changed); + + // 初始化界面数据 + InitCameraParameters(); +} + +DialogCamera::~DialogCamera() +{ + delete ui; +} + +void DialogCamera::InitCameraComboBox() +{ + // 清空ComboBox + ui->combo_camera->clear(); + + // 添加相机选项 + for (size_t i = 0; i < m_deviceList.size(); ++i) { + ui->combo_camera->addItem(QString::fromStdString(m_deviceList[i].first)); + } + + // 默认选择第一个 + if (!m_deviceList.empty()) { + ui->combo_camera->setCurrentIndex(0); + } +} + +void DialogCamera::on_camera_selection_changed(int index) +{ + if (index >= 0 && index < static_cast(m_deviceList.size())) { + m_currentCameraIndex = index; + m_currentDevice = m_deviceList[index].second; + + // 重新加载当前选择相机的参数 + InitCameraParameters(); + + LOG_DEBUG("Camera selection changed to index: %d\n", index); + } +} + +void DialogCamera::on_btn_camer_ok_clicked() +{ + if (!m_currentDevice) { + QMessageBox::warning(this, "错误", "设备未初始化"); + return; + } + + // 应用参数配置 + if (ApplyCameraParameters()) { + QMessageBox::information(this, "成功", "相机参数配置成功!"); + accept(); // 关闭对话框并返回Accepted + } else { + QMessageBox::warning(this, "失败", "相机参数配置失败,请检查设备连接!"); + } +} + +void DialogCamera::on_btn_camer_cancel_clicked() +{ + // 直接关闭窗口,不保存任何更改 + reject(); +} + +void DialogCamera::InitCameraParameters() +{ + if (!m_currentDevice) return; + + try { + // 获取曝光时间 + unsigned int exposeTime = 0; + if (m_currentDevice->GetEyeExpose(exposeTime) == 0) { + ui->lineEdit_export->setText(QString::number(exposeTime)); + LOG_DEBUG("Current expose time: %u\n", exposeTime); + } + + // 获取增益 + unsigned int gain = 0; + if (m_currentDevice->GetEyeGain(gain) == 0) { + ui->lineEdit_gain->setText(QString::number(gain)); + LOG_DEBUG("Current gain: %u\n", gain); + } + + // 获取帧率 + int frame = 0; + if (m_currentDevice->GetFrame(frame) == 0) { + ui->lineEdit_frame->setText(QString::number(frame)); + LOG_DEBUG("Current frame rate: %d\n", frame); + } + + // 获取摆动速度 + float swingSpeed = 0.0f; + if (m_currentDevice->GetSwingSpeed(swingSpeed) == 0) { + ui->lineEdit_swing_speed->setText(QString::number(swingSpeed)); + LOG_DEBUG("Swing speed: %.3f\n", swingSpeed); + } + + // 获取工作角度 + float minAngle = 0.0f, maxAngle = 0.0f; + if (m_currentDevice->GetSwingAngle(minAngle, maxAngle) == 0) { + ui->lineEdit_swing_start->setText(QString::number(minAngle)); + ui->lineEdit_swing_stop->setText(QString::number(maxAngle)); + LOG_DEBUG("Swing angle range: %.3f to %.3f\n", minAngle, maxAngle); + } + + } catch (...) { + QMessageBox::critical(this, "错误", "读取相机参数时发生异常"); + } +} + +bool DialogCamera::ApplyCameraParameters() +{ + if (!m_currentDevice) return false; + + try { + bool success = true; + + // 设置曝光时间 + unsigned int exposeTime = ui->lineEdit_export->text().toUInt(); + if (m_currentDevice->SetEyeExpose(exposeTime) != 0) { + LOG_WARNING("Failed to set expose time: %u\n", exposeTime); + success = false; + } else { + LOG_INFO("Set expose time: %u\n", exposeTime); + } + + // 设置增益 + unsigned int gain = ui->lineEdit_gain->text().toUInt(); + if (m_currentDevice->SetEyeGain(gain) != 0) { + LOG_WARNING("Failed to set gain: %u\n", gain); + success = false; + } else { + LOG_INFO("Set gain: %u\n", gain); + } + + // 设置帧率 + int frame = ui->lineEdit_frame->text().toInt(); + if (m_currentDevice->SetFrame(frame) != 0) { + LOG_WARNING("Failed to set frame rate: %d\n", frame); + success = false; + } else { + LOG_INFO("Set frame rate: %d\n", frame); + } + + // 设置摆动速度 + float swingSpeed = ui->lineEdit_swing_speed->text().toFloat(); + if (m_currentDevice->SetSwingSpeed(swingSpeed) != 0) { + LOG_WARNING("Failed to set swing speed: %.3f\n", swingSpeed); + success = false; + } else { + LOG_INFO("Set swing speed: %.3f\n", swingSpeed); + } + + // 设置工作角度 + float minAngle = ui->lineEdit_swing_start->text().toFloat(); + float maxAngle = ui->lineEdit_swing_stop->text().toFloat(); + if (m_currentDevice->SetSwingAngle(minAngle, maxAngle) != 0) { + LOG_WARNING("Failed to set swing angle: %.3f to %.3f\n", minAngle, maxAngle); + success = false; + } else { + LOG_INFO("Set swing angle: %.3f to %.3f\n", minAngle, maxAngle); + } + + return success; + + } catch (...) { + QMessageBox::critical(this, "错误", "应用相机参数时发生异常"); + return false; + } +} + diff --git a/App/LapWeld/LapWeldApp/dialogcamera.h b/App/LapWeld/LapWeldApp/dialogcamera.h new file mode 100644 index 0000000..51d6f38 --- /dev/null +++ b/App/LapWeld/LapWeldApp/dialogcamera.h @@ -0,0 +1,45 @@ +#ifndef DIALOGCAMERA_H +#define DIALOGCAMERA_H + +#include +#include +#include +#include + +namespace Ui { +class DialogCamera; +} + +class DialogCamera : public QDialog +{ + Q_OBJECT + +public: + // 支持多相机的构造函数 + explicit DialogCamera(const std::vector>& deviceList, + QWidget *parent = nullptr); + ~DialogCamera(); + +private slots: + void on_btn_camer_ok_clicked(); + void on_btn_camer_cancel_clicked(); + void on_camera_selection_changed(int index); + +private: + // 初始化相机参数到界面 + void InitCameraParameters(); + + // 应用界面参数到相机 + bool ApplyCameraParameters(); + + // 初始化相机选择下拉框 + void InitCameraComboBox(); + +private: + Ui::DialogCamera *ui; + std::vector> m_deviceList; // 相机设备列表 + IVrEyeDevice* m_currentDevice = nullptr; // 当前选择的相机设备 + int m_currentCameraIndex = 0; // 当前选择的相机索引 +}; + +#endif // DIALOGCAMERA_H diff --git a/App/LapWeld/LapWeldApp/dialogcamera.ui b/App/LapWeld/LapWeldApp/dialogcamera.ui new file mode 100644 index 0000000..b038a27 --- /dev/null +++ b/App/LapWeld/LapWeldApp/dialogcamera.ui @@ -0,0 +1,394 @@ + + + DialogCamera + + + + 0 + 0 + 660 + 400 + + + + 相机配置 + + + background-color: rgb(25, 26, 28); + + + + + 50 + 10 + 491 + 41 + + + + + 18 + + + + color: rgb(221, 225, 233); + + + 相机配置 + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + + + + + + 50 + 110 + 221 + 221 + + + + + 18 + + + + color: rgb(221, 225, 233); + + + 基本参数 + + + + + 20 + 40 + 181 + 171 + + + + + 18 + + + + + + + + + + 18 + + + + color: rgb(221, 225, 233); + + + 曝光 + + + + + + + + 18 + + + + color: rgb(221, 225, 233); +background-color: rgb(47, 48, 52); + + + Qt::InputMethodHint::ImhDigitsOnly + + + + + + + + + + + + 18 + + + + color: rgb(221, 225, 233); + + + 增益 + + + + + + + + 18 + + + + color: rgb(221, 225, 233); +background-color: rgb(47, 48, 52); + + + Qt::InputMethodHint::ImhDigitsOnly + + + + + + + + + + + + 18 + + + + color: rgb(221, 225, 233); + + + 帧率 + + + + + + + + 18 + + + + color: rgb(221, 225, 233); +background-color: rgb(47, 48, 52); + + + Qt::InputMethodHint::ImhDigitsOnly + + + + + + + + + + + + 310 + 110 + 281 + 221 + + + + + 18 + + + + color: rgb(221, 225, 233); + + + 扫描设置 + + + + + 20 + 40 + 241 + 171 + + + + + 18 + + + + + + + + + + 18 + + + + color: rgb(221, 225, 233); + + + 摆动速度 + + + + + + + + 18 + + + + color: rgb(221, 225, 233); +background-color: rgb(47, 48, 52); + + + Qt::InputMethodHint::ImhDigitsOnly + + + + + + + + + + + + 18 + + + + color: rgb(221, 225, 233); + + + 开始角度 + + + + + + + + 18 + + + + color: rgb(221, 225, 233); +background-color: rgb(47, 48, 52); + + + Qt::InputMethodHint::ImhDigitsOnly + + + + + + + + + + + + 18 + + + + color: rgb(221, 225, 233); + + + 结束角度 + + + + + + + + 18 + + + + color: rgb(221, 225, 233); +background-color: rgb(47, 48, 52); + + + Qt::InputMethodHint::ImhDigitsOnly + + + + + + + + + + + + 190 + 350 + 111 + 38 + + + + + 18 + + + + image: url(:/resource/dialog_ok.png); + + + + + + + + + 340 + 350 + 111 + 38 + + + + + 18 + + + + image: url(:/resource/dialog_cancel.png); + + + + + + + + + 50 + 60 + 211 + 41 + + + + + 16 + + + + color: rgb(221, 225, 233); +selection-color: rgb(255, 255, 255); +background-color: rgb(47, 48, 52); + + + + + + diff --git a/App/LapWeld/LapWeldApp/dialogcameralevel.cpp b/App/LapWeld/LapWeldApp/dialogcameralevel.cpp new file mode 100644 index 0000000..f2a6180 --- /dev/null +++ b/App/LapWeld/LapWeldApp/dialogcameralevel.cpp @@ -0,0 +1,839 @@ +#include "dialogcameralevel.h" +#include "ui_dialogcameralevel.h" +#include "IVrUtils.h" +#include "PathManager.h" +#include +#include +#include +#include +#include + +#include "LaserDataLoader.h" +#include +#include +#include +#include +#include +#include "LapWeldPresenter.h" + +DialogCameraLevel::DialogCameraLevel(QWidget *parent) + : QDialog(parent) + , ui(new Ui::DialogCameraLevel) +{ + ui->setupUi(this); + + // 隐藏标题栏 + setWindowFlags(Qt::FramelessWindowHint); + + // 初始化界面 + initializeCameraCombo(); + + // 初始化结果显示区域 + ui->label_level_result->setText("请选择相机并点击调平按钮\n开始相机调平操作"); + ui->label_level_result->setAlignment(Qt::AlignCenter); +} + +DialogCameraLevel::~DialogCameraLevel() +{ + // 清理扫描数据缓存 + clearScanDataCache(); + + // 确保恢复Presenter的状态回调 + restorePresenterStatusCallback(); + + delete ui; +} + +void DialogCameraLevel::setCameraList(const std::vector>& cameraList, + LapWeldPresenter* presenter) +{ + m_cameraList = cameraList; + m_presenter = presenter; + + // 重新初始化相机选择框 + initializeCameraCombo(); +} + +void DialogCameraLevel::initializeCameraCombo() +{ + ui->combo_camera->clear(); + + if (m_cameraList.empty()) { + ui->combo_camera->setEnabled(false); + ui->label_level_result->setText("无可用相机设备"); + ui->label_level_result->setAlignment(Qt::AlignCenter); + } else { + for (const auto& camera : m_cameraList) { + ui->combo_camera->addItem(QString::fromStdString(camera.first)); + } + ui->combo_camera->setEnabled(true); + + // 检查并显示当前选中相机的标定状态 + checkAndDisplayCalibrationStatus(0); // 默认选中第一个相机 + } +} + +void DialogCameraLevel::on_btn_apply_clicked() +{ + ui->label_level_result->setAlignment(Qt::AlignLeft); + +#ifndef LEVEL_DEBUG_MODE + // 检查是否有可用的相机 + if (m_cameraList.empty()) { + QMessageBox::warning(this, "错误", "无可用相机设备!"); + return; + } + + // 获取选中的相机 + int selectedIndex = ui->combo_camera->currentIndex(); + if (selectedIndex < 0 || selectedIndex >= m_cameraList.size()) { + QMessageBox::warning(this, "错误", "请选择有效的相机!"); + return; + } +#endif + + // 清空之前的结果显示 + ui->label_level_result->setText("调平计算中,请稍候..."); + + // 显示进度提示 + ui->btn_apply->setEnabled(false); + QApplication::processEvents(); + + try { + // 执行相机调平 + if (performCameraLeveling()) { + // 调平成功,关闭对话框(这会触发析构函数中的回调恢复) + // accept(); + } else { + // 显示失败信息到界面 + ui->label_level_result->setText("调平失败!\n\n请检查:\n1. 相机连接是否正常\n2. 地面扫描数据是否充足\n3. 扫描区域是否有足够的地面"); + } + } catch (const std::exception& e) { + LOG_ERROR("Camera leveling failed with exception: %s\n", e.what()); + QMessageBox::critical(this, "错误", QString("调平过程发生异常:%1").arg(e.what())); + } + + // 恢复按钮状态 + ui->btn_apply->setEnabled(true); +} + +void DialogCameraLevel::on_btn_cancel_clicked() +{ + // 直接关闭窗口,不保存任何更改 + reject(); +} + +bool DialogCameraLevel::performCameraLeveling() +{ + try { + // 获取选中的相机索引 + int selectedIndex = ui->combo_camera->currentIndex(); + + // 1. 设置调平状态回调 + setLevelingStatusCallback(); + +#ifdef LEVEL_DEBUG_MODE + // Debug模式:使用文件对话框选择测试数据 + LOG_INFO("=== DEBUG MODE ENABLED ===\n"); + + // 选择debug数据文件 + QString debugDataFile = selectDebugDataFile(); + if (debugDataFile.isEmpty()) { + LOG_INFO("Debug data file selection cancelled\n"); + return false; + } + + // 使用选择的debug数据进行模拟扫描 + if (!loadDebugDataAndSimulateScan(debugDataFile)) { + LOG_ERROR("Failed to load debug data for camera leveling\n"); + return false; + } +#else + // 正常模式:使用真实相机 + if (selectedIndex < 0 || selectedIndex >= m_cameraList.size()) { + LOG_ERROR("Invalid camera index: %d\n", selectedIndex); + return false; + } + + LOG_INFO("Performing camera leveling with camera %d (index %d)\n", selectedIndex + 1, selectedIndex); + + // 2. 清空之前的扫描数据 + clearScanDataCache(); + + // 3. 启动相机扫描地面数据 + if (!startCameraScan(selectedIndex)) { + LOG_ERROR("Failed to start camera scan for leveling\n"); + return false; + } + + // 4. 等待扫描完成(使用状态回调判断) + LOG_INFO("Collecting ground scan data, waiting for swing finish signal...\n"); + int waitTime = 0; + const int maxWaitTime = 10000; // 最大等待10秒 + const int checkInterval = 100; // 每100ms检查一次 + + while (!m_swingFinished && waitTime < maxWaitTime) { + QThread::msleep(checkInterval); + QApplication::processEvents(); // 处理UI事件 + waitTime += checkInterval; + } + + // 5. 停止扫描 + stopCameraScan(selectedIndex); + + // 检查是否通过状态回调收到了扫描完成信号 + if (m_swingFinished) { + LOG_INFO("Camera swing finished signal received, scan completed\n"); + } else if (waitTime >= maxWaitTime) { + LOG_WARNING("Timeout waiting for camera swing finish signal\n"); + } +#endif + + // 5. 检查是否收集到足够的数据 + // 6. 调用调平算法计算 + double planeCalib[9]; + double planeHeight; + double invRMatrix[9]; + + if (!calculatePlaneCalibration(planeCalib, planeHeight, invRMatrix)) { + LOG_ERROR("Failed to calculate plane calibration\n"); + return false; + } + + LOG_INFO("Camera leveling calculation completed\n"); + + // 7. 更新界面显示 + updateLevelingResults(planeCalib, planeHeight, invRMatrix); + + // 8. 保存结果到配置 + // 获取相机索引和名称传递给保存方法 + int cameraIndex = selectedIndex + 1; // 转换为1-based索引 + QString cameraName; + +#ifdef LEVEL_DEBUG_MODE + // Debug模式下使用默认名称 + cameraIndex = 1; + cameraName = QString("Camera_%1").arg(cameraIndex); +#else + // 正常模式下从列表获取名称 + if (selectedIndex >= 0 && selectedIndex < m_cameraList.size()) { + cameraName = QString::fromStdString(m_cameraList[selectedIndex].first); + } else { + cameraName = QString("Camera_%1").arg(cameraIndex); + } +#endif + + if (!saveLevelingResults(planeCalib, planeHeight, invRMatrix, cameraIndex, cameraName)) { + LOG_ERROR("Failed to save leveling results\n"); + return false; + } + + clearScanDataCache(); + LOG_INFO("Camera leveling completed successfully\n"); + + return true; + + } catch (const std::exception& e) { + LOG_ERROR("Exception in performCameraLeveling: %s\n", e.what()); + + return false; + } +} + +void DialogCameraLevel::updateLevelingResults(double planeCalib[9], double planeHeight, double invRMatrix[9]) +{ + // 计算旋转角度的近似值用于显示 + double rotX = atan2(planeCalib[5], planeCalib[8]) * 180.0 / M_PI; + double rotY = atan2(-planeCalib[2], sqrt(planeCalib[5]*planeCalib[5] + planeCalib[8]*planeCalib[8])) * 180.0 / M_PI; + + // 构建显示文本,将矩阵直接显示到页面上 + QString resultText; +#if 0 + // 基本信息 + resultText += QString("旋转角度 X: %1°\n").arg(QString::number(rotX, 'f', 2)); + resultText += QString("旋转角度 Y: %1°\n").arg(QString::number(rotY, 'f', 2)); +#endif + + resultText += QString("地面高度: %1 mm\n").arg(QString::number(planeHeight, 'f', 2)); + + // 调平矩阵 + resultText += QString("调平矩阵:\n"); + for (int i = 0; i < 3; i++) { + resultText += QString("[%1, %2, %3]\n") + .arg(QString::number(planeCalib[i*3], 'f', 4)) + .arg(QString::number(planeCalib[i*3+1], 'f', 4)) + .arg(QString::number(planeCalib[i*3+2], 'f', 4)); + } + + resultText += QString("逆旋转矩阵:\n"); + for (int i = 0; i < 3; i++) { + resultText += QString("[%1, %2, %3]\n") + .arg(QString::number(invRMatrix[i*3], 'f', 4)) + .arg(QString::number(invRMatrix[i*3+1], 'f', 4)) + .arg(QString::number(invRMatrix[i*3+2], 'f', 4)); + } + + // 将结果显示到界面上 + ui->label_level_result->setText(resultText); + ui->label_level_result->setAlignment(Qt::AlignLeft | Qt::AlignTop); +} + +// 启动相机扫描 +bool DialogCameraLevel::startCameraScan(int cameraIndex) +{ + if (cameraIndex < 0 || cameraIndex >= m_cameraList.size()) { + LOG_ERROR("Invalid camera index for scan: %d\n", cameraIndex); + return false; + } + + IVrEyeDevice* camera = m_cameraList[cameraIndex].second; + if (!camera) { + LOG_ERROR("Camera device is null at index: %d\n", cameraIndex); + return false; + } + + // 启动相机检测,使用静态回调函数 + int result = camera->StartDetect(&DialogCameraLevel::StaticDetectionCallback, keResultDataType_Position, this); + if (result != 0) { + LOG_ERROR("Failed to start camera detection: %d\n", result); + return false; + } + + LOG_INFO("Camera scan started successfully for camera index: %d\n", cameraIndex); + return true; +} + +// 停止相机扫描 +bool DialogCameraLevel::stopCameraScan(int cameraIndex) +{ + if (cameraIndex < 0 || cameraIndex >= m_cameraList.size()) { + LOG_ERROR("Invalid camera index for stop scan: %d\n", cameraIndex); + return false; + } + + IVrEyeDevice* camera = m_cameraList[cameraIndex].second; + if (!camera) { + LOG_ERROR("Camera device is null at index: %d\n", cameraIndex); + return false; + } + + int result = camera->StopDetect(); + if (result != 0) { + LOG_WARNING("Failed to stop camera detection, error: %d\n", result); + return false; + } + + LOG_INFO("Camera scan stopped successfully for camera index: %d\n", cameraIndex); + return true; +} + +// 静态检测回调函数 +void DialogCameraLevel::StaticDetectionCallback(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint, void* pUserData) +{ + DialogCameraLevel* pThis = reinterpret_cast(pUserData); + if (pThis && pLaserLinePoint) { + pThis->DetectionCallback(eDataType, pLaserLinePoint); + } +} + +// 静态状态回调函数 +void DialogCameraLevel::StaticStatusCallback(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pInfoParam) +{ + DialogCameraLevel* pThis = reinterpret_cast(pInfoParam); + if (pThis) { + pThis->StatusCallback(eStatus, pExtData, nDataLength, pInfoParam); + } +} + +// 状态回调函数实例版本 +void DialogCameraLevel::StatusCallback(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pInfoParam) +{ + LOG_DEBUG("[Leveling Status Callback] received: status=%d\n", (int)eStatus); + + switch (eStatus) { + case EVzDeviceWorkStatus::keDeviceWorkStatus_Device_Swing_Finish: + { + LOG_INFO("[Leveling Status Callback] Camera swing finished, scan completed\n"); + m_swingFinished = true; // 摆动完成即表示扫描完成 + break; + } + default: + LOG_DEBUG("[Leveling Status Callback] Other status: %d\n", (int)eStatus); + break; + } +} + +// 检测数据回调函数实例版本 +void DialogCameraLevel::DetectionCallback(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint) +{ + if (!pLaserLinePoint) { + LOG_WARNING("[Leveling Callback] pLaserLinePoint is null\n"); + return; + } + + if (pLaserLinePoint->nPointCount <= 0) { + LOG_WARNING("[Leveling Callback] Point count is zero or negative: %d\n", pLaserLinePoint->nPointCount); + return; + } + + if (!pLaserLinePoint->p3DPoint) { + LOG_WARNING("[Leveling Callback] p3DPoint is null\n"); + return; + } + + // 转换数据格式:从SVzLaserLineData转换为SVzNL3DLaserLine并存储到缓存 + SVzNL3DLaserLine laser3DLine; + + // 复制基本信息 + laser3DLine.nTimeStamp = pLaserLinePoint->llTimeStamp; + laser3DLine.nPositionCnt = pLaserLinePoint->nPointCount; + + // 分配和复制点云数据 + laser3DLine.p3DPosition = new SVzNL3DPosition[pLaserLinePoint->nPointCount]; + // 复制点云数据 + memcpy(laser3DLine.p3DPosition, pLaserLinePoint->p3DPoint, sizeof(SVzNL3DPosition) * pLaserLinePoint->nPointCount); + + // 将转换后的数据保存到缓存中 + std::lock_guard lock(m_scanDataMutex); + m_scanDataCache.push_back(laser3DLine); +} + +// 调平算法计算 +bool DialogCameraLevel::calculatePlaneCalibration(double planeCalib[9], double& planeHeight, double invRMatrix[9]) +{ + std::lock_guard lock(m_scanDataMutex); + // 检查是否有足够的扫描数据 + if (m_scanDataCache.empty()) { + LOG_ERROR("No scan data available for plane calibration\n"); + return false; + } + + LOG_INFO("Calculating plane calibration from %zu scan lines\n", m_scanDataCache.size()); + + try { + // 调用实际的调平算法 + SSG_planeCalibPara calibResult;// = sx_getBaseCalibPara(m_scanDataCache.data(), static_cast(m_scanDataCache.size())); + + // 将结构体中的数据复制到输出参数 + for (int i = 0; i < 9; i++) { + planeCalib[i] = calibResult.planeCalib[i]; + invRMatrix[i] = calibResult.invRMatrix[i]; + } + planeHeight = calibResult.planeHeight; + + // 计算旋转角度用于日志显示 + double rotAngleX = atan2(planeCalib[5], planeCalib[8]) * 180.0 / M_PI; + double rotAngleY = atan2(-planeCalib[2], sqrt(planeCalib[5]*planeCalib[5] + planeCalib[8]*planeCalib[8])) * 180.0 / M_PI; + + LOG_INFO("Plane calibration calculated: height=%.3f, rotX=%.2f°, rotY=%.2f°\n", + planeHeight, rotAngleX, rotAngleY); + +#ifdef LEVEL_DEBUG_MODE + // 统计点云数据 + int totalPoints = 0; + double avgHeight = 0.0; + for (const auto& scanLine : m_scanDataCache) { + for (int i = 0; i < scanLine.nPositionCnt; i++) { + if (scanLine.p3DPosition[i].pt3D.z > -1000 && scanLine.p3DPosition[i].pt3D.z < 1000) { + avgHeight += scanLine.p3DPosition[i].pt3D.z; + totalPoints++; + } + } + } + + LOG_INFO("=== DEBUG MODE CALIBRATION RESULTS ===\n"); + + LOG_INFO("Algorithm results:\n"); + LOG_INFO(" Calculated plane height: %.3f mm\n", calibResult.planeHeight); + LOG_INFO("Calibration matrix:\n"); + for (int i = 0; i < 3; i++) { + LOG_INFO(" [%.6f, %.6f, %.6f]\n", calibResult.planeCalib[i*3], calibResult.planeCalib[i*3+1], calibResult.planeCalib[i*3+2]); + } + LOG_INFO("Inverse rotation matrix:\n"); + for (int i = 0; i < 3; i++) { + LOG_INFO(" [%.6f, %.6f, %.6f]\n", calibResult.invRMatrix[i*3], calibResult.invRMatrix[i*3+1], calibResult.invRMatrix[i*3+2]); + } + LOG_INFO("=======================================\n"); +#endif + + return true; + + } catch (const std::exception& e) { + LOG_ERROR("Exception in sg_getBagBaseCalibPara: %s\n", e.what()); + return false; + } catch (...) { + LOG_ERROR("Unknown exception in sg_getBagBaseCalibPara\n"); + return false; + } +} + +// 清空扫描数据缓存 +void DialogCameraLevel::clearScanDataCache() +{ + std::lock_guard lock(m_scanDataMutex); + + LOG_DEBUG("Clearing scan data cache, current size: %zu\n", m_scanDataCache.size()); + + // 释放缓存的内存 + for (auto& cachedLine : m_scanDataCache) { + if (cachedLine.p3DPosition) { + delete[] cachedLine.p3DPosition; + cachedLine.p3DPosition = nullptr; + } + } + + // 清空缓存容器 + m_scanDataCache.clear(); + + LOG_DEBUG("Scan data cache cleared successfully\n"); +} + +bool DialogCameraLevel::saveLevelingResults(double planeCalib[9], double planeHeight, double invRMatrix[9], int cameraIndex, const QString& cameraName) +{ + try { + if (!m_presenter) { + LOG_ERROR("Presenter is null, cannot save leveling results\n"); + return false; + } + + // 获取配置对象 + IVrConfig* config = m_presenter->GetConfig(); + if (!config) { + LOG_ERROR("Config is null, cannot save leveling results\n"); + return false; + } + + // 验证传入的相机参数 + if (cameraIndex <= 0) { + LOG_ERROR("Invalid camera index: %d\n", cameraIndex); + return false; + } + + if (cameraName.isEmpty()) { + LOG_ERROR("Camera name is empty\n"); + return false; + } + + // 加载当前配置 + QString configPath = PathManager::GetConfigFilePath(); + LOG_INFO("Config path: %s\n", configPath.toUtf8().constData()); + ConfigResult configResult = config->LoadConfig(configPath.toStdString()); + + // 创建或更新指定相机的调平参数 + VrCameraPlaneCalibParam cameraParam; + cameraParam.cameraIndex = cameraIndex; + cameraParam.cameraName = cameraName.toStdString(); + cameraParam.planeHeight = planeHeight; + cameraParam.isCalibrated = true; + + // 复制校准矩阵 + for (int i = 0; i < 9; i++) { + cameraParam.planeCalib[i] = planeCalib[i]; + cameraParam.invRMatrix[i] = invRMatrix[i]; + } + + // 更新配置中的相机校准参数 + configResult.algorithmParams.planeCalibParam.SetCameraCalibParam(cameraParam); + + // 保存配置 + bool saveResult = config->SaveConfig(configPath.toStdString(), configResult); + if (!saveResult) { + LOG_ERROR("Failed to save config with leveling results\n"); + return false; + } + + LOG_INFO("Leveling results saved successfully for camera %d (%s)\n", cameraIndex, cameraName.toUtf8().constData()); + LOG_INFO("Plane height: %.3f\n", planeHeight); + LOG_INFO("Calibration marked as completed\n"); + + return true; + + } catch (const std::exception& e) { + LOG_ERROR("Exception in saveLevelingResults: %s\n", e.what()); + return false; + } +} + +// 选择debug数据文件 +QString DialogCameraLevel::selectDebugDataFile() +{ + // 获取应用程序目录作为起始目录 + QString appDir = QCoreApplication::applicationDirPath(); + QString testDataDir = appDir + "/TestData"; + + // 如果TestData目录不存在,使用应用程序目录 + if (!QFileInfo::exists(testDataDir)) { + testDataDir = appDir; + } + + // 打开文件选择对话框 + QString fileName = QFileDialog::getOpenFileName( + this, + "选择激光扫描数据文件", + testDataDir, + "文本文件 (*.txt);;所有文件 (*.*)" + ); + + if (fileName.isEmpty()) { + LOG_INFO("No debug data file selected\n"); + return ""; + } + + // 检查文件是否存在 + if (!QFileInfo::exists(fileName)) { + LOG_ERROR("Selected debug data file does not exist: %s\n", fileName.toUtf8().constData()); + QMessageBox::warning(this, "文件错误", "选择的文件不存在!"); + return ""; + } + + LOG_INFO("Selected debug data file: %s\n", fileName.toUtf8().constData()); + + return fileName; +} + +// 加载debug数据并模拟扫描过程 +bool DialogCameraLevel::loadDebugDataAndSimulateScan(const QString& filePath) +{ + LOG_INFO("Loading debug data from: %s\n", filePath.toUtf8().constData()); + + try { + // 清空之前的扫描数据 + clearScanDataCache(); + + // 使用LaserDataLoader加载测试数据 + LaserDataLoader dataLoader; + std::vector> debugData; + int lineNum = 0; + float scanSpeed = 0.0f; + int maxTimeStamp = 0; + int clockPerSecond = 0; + + int result = dataLoader.LoadLaserScanData(filePath.toStdString(), debugData, + lineNum, scanSpeed, maxTimeStamp, clockPerSecond); + + if (result != 0) { + LOG_ERROR("Failed to load debug data: %s\n", dataLoader.GetLastError().c_str()); + QMessageBox::critical(this, "Debug模式错误", + QString("加载测试数据失败:\n%1").arg(QString::fromStdString(dataLoader.GetLastError()))); + return false; + } + + if (debugData.empty()) { + LOG_ERROR("Debug data is empty\n"); + QMessageBox::warning(this, "Debug模式警告", "测试数据文件为空!"); + return false; + } + + LOG_INFO("Loaded %d lines of debug data, starting simulation\n", lineNum); + + // 将加载的数据复制到缓存中(需要深拷贝) + { + std::lock_guard lock(m_scanDataMutex); + m_scanDataCache.reserve(debugData.size()); + + for (const auto& linePair : debugData) { + EVzResultDataType dataType = linePair.first; + const SVzLaserLineData& lineData = linePair.second; + + // 只处理Position类型的数据(相机调平只需要位置信息) + if (dataType == keResultDataType_Position && lineData.p3DPoint) { + SVzNL3DLaserLine copyLine; + copyLine.nTimeStamp = 0; // 从SVzLaserLineData中获取时间戳 + copyLine.nPositionCnt = lineData.nPointCount; + + if (lineData.nPointCount > 0) { + copyLine.p3DPosition = new SVzNL3DPosition[lineData.nPointCount]; + memcpy(copyLine.p3DPosition, lineData.p3DPoint, sizeof(SVzNL3DPosition) * lineData.nPointCount); + } else { + copyLine.p3DPosition = nullptr; + } + + m_scanDataCache.push_back(copyLine); + } + } + } + + // 释放临时加载的数据 + dataLoader.FreeLaserScanData(debugData); + + // 启动模拟扫描过程 + simulateScanProcess(); + + return true; + + } catch (const std::exception& e) { + LOG_ERROR("Exception in loadDebugDataAndSimulateScan: %s\n", e.what()); + QMessageBox::critical(this, "Debug模式异常", QString("加载debug数据时发生异常:\n%1").arg(e.what())); + return false; + } +} + +// 模拟扫描过程 +void DialogCameraLevel::simulateScanProcess() +{ + LOG_INFO("Starting scan simulation process\n"); + + // 创建定时器来模拟数据收集过程 + QTimer* simulationTimer = new QTimer(); + simulationTimer->setSingleShot(true); + + // 模拟扫描需要的时间(2秒) + simulationTimer->setInterval(2000); + + // 连接定时器信号 + connect(simulationTimer, &QTimer::timeout, [this, simulationTimer]() { + LOG_INFO("Scan simulation completed\n"); + m_swingFinished = true; // Debug模式下模拟摆动完成 + + // 显示调试信息 + { + std::lock_guard lock(m_scanDataMutex); + LOG_INFO("Debug scan simulation completed with %zu lines\n", m_scanDataCache.size()); + + // 统计点云数据 + int totalPoints = 0; + for (const auto& line : m_scanDataCache) { + totalPoints += line.nPositionCnt; + } + LOG_INFO("Total points in debug data: %d\n", totalPoints); + } + + // 清理定时器 + simulationTimer->deleteLater(); + }); + + // 启动模拟扫描 + simulationTimer->start(); + + LOG_INFO("Simulation timer started, waiting for completion...\n"); +} + +// 相机选择改变的槽函数 +void DialogCameraLevel::on_combo_camera_currentIndexChanged(int index) +{ + if (index >= 0 && index < m_cameraList.size()) { + LOG_INFO("Camera selection changed to index: %d (%s)\n", index, + QString::fromStdString(m_cameraList[index].first).toUtf8().constData()); + checkAndDisplayCalibrationStatus(index); + } else { + LOG_WARNING("Invalid camera index selected: %d\n", index); + ui->label_level_result->setText("无效的相机选择"); + ui->label_level_result->setAlignment(Qt::AlignCenter); + } +} + +// 加载相机标定数据 +bool DialogCameraLevel::loadCameraCalibrationData(int cameraIndex, const QString& cameraName, + double planeCalib[9], double& planeHeight, double invRMatrix[9]) +{ + try { + if (!m_presenter) { + LOG_ERROR("Presenter is null, cannot load calibration data\n"); + return false; + } + + // 获取配置对象 + IVrConfig* config = m_presenter->GetConfig(); + if (!config) { + LOG_ERROR("Config is null, cannot load calibration data\n"); + return false; + } + + // 加载配置文件 + QString configPath = PathManager::GetConfigFilePath(); + ConfigResult configResult = config->LoadConfig(configPath.toStdString()); + + // 获取指定相机的标定参数 + const VrCameraPlaneCalibParam* cameraParam = configResult.algorithmParams.planeCalibParam.GetCameraCalibParam(cameraIndex); + + if (!cameraParam || !cameraParam->isCalibrated) { + LOG_INFO("No calibration data found for camera %d (%s)\n", cameraIndex, cameraName.toUtf8().constData()); + return false; + } + + // 复制标定数据 + for (int i = 0; i < 9; i++) { + planeCalib[i] = cameraParam->planeCalib[i]; + invRMatrix[i] = cameraParam->invRMatrix[i]; + } + planeHeight = cameraParam->planeHeight; + + LOG_INFO("Calibration data loaded successfully for camera %d (%s)\n", cameraIndex, cameraName.toUtf8().constData()); + LOG_INFO("Plane height: %.3f\n", planeHeight); + + return true; + + } catch (const std::exception& e) { + LOG_ERROR("Exception in loadCameraCalibrationData: %s\n", e.what()); + return false; + } +} + +// 检查并显示相机标定状态 +void DialogCameraLevel::checkAndDisplayCalibrationStatus(int cameraIndex) +{ + if (cameraIndex < 0 || cameraIndex >= m_cameraList.size()) { + LOG_WARNING("Invalid camera index for status check: %d\n", cameraIndex); + ui->label_level_result->setText("无效的相机索引"); + ui->label_level_result->setAlignment(Qt::AlignCenter); + return; + } + + QString cameraName = QString::fromStdString(m_cameraList[cameraIndex].first); + int configCameraIndex = cameraIndex + 1; // 转换为1-based索引 + + // 尝试加载该相机的标定数据 + double planeCalib[9]; + double planeHeight; + double invRMatrix[9]; + + if (loadCameraCalibrationData(configCameraIndex, cameraName, planeCalib, planeHeight, invRMatrix)) { + // 有标定数据,显示标定结果 + LOG_INFO("Displaying existing calibration data for camera %s\n", cameraName.toUtf8().constData()); + updateLevelingResults(planeCalib, planeHeight, invRMatrix); + } else { + // 没有标定数据,显示提示信息 + LOG_INFO("No calibration data found for camera %s, showing instruction\n", cameraName.toUtf8().constData()); + ui->label_level_result->setText(QString("请选择相机 \"%1\" 并点击调平按钮\n开始相机调平操作").arg(cameraName)); + ui->label_level_result->setAlignment(Qt::AlignCenter); + } +} + +// 设置调平时的状态回调 +void DialogCameraLevel::setLevelingStatusCallback() +{ + if (!m_presenter) { + LOG_ERROR("Presenter is null, cannot set leveling status callback\n"); + return; + } + + // 为所有相机设置调平状态回调 + m_presenter->SetCameraStatusCallback(&DialogCameraLevel::StaticStatusCallback, this); + + // 重置状态标志 + m_swingFinished = false; + m_callbackRestored = false; // 重置恢复标志,允许稍后恢复 + + LOG_INFO("Leveling status callback set for all cameras\n"); +} + +// 恢复Presenter的状态回调 +void DialogCameraLevel::restorePresenterStatusCallback() +{ + // 检查是否已经恢复过回调,避免重复调用 + if (m_callbackRestored.exchange(true)) { + LOG_DEBUG("Presenter status callback already restored, skipping\n"); + return; + } + + if (!m_presenter) { + LOG_ERROR("Presenter is null, cannot restore status callback\n"); + return; + } + + // 恢复Presenter的状态回调 - 使用LapWeldPresenter的静态回调 + m_presenter->SetCameraStatusCallback(&LapWeldPresenter::_StaticCameraNotify, m_presenter); + + LOG_INFO("Presenter status callback restored for all cameras\n"); +} + diff --git a/App/LapWeld/LapWeldApp/dialogcameralevel.h b/App/LapWeld/LapWeldApp/dialogcameralevel.h new file mode 100644 index 0000000..247f00d --- /dev/null +++ b/App/LapWeld/LapWeldApp/dialogcameralevel.h @@ -0,0 +1,106 @@ +#ifndef DialogCameraLevel_H +#define DialogCameraLevel_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include "IVrEyeDevice.h" +#include "LapWeldPresenter.h" +#include "VZNL_Types.h" + + +// #define LEVEL_DEBUG_MODE + +namespace Ui { +class DialogCameraLevel; +} + +class DialogCameraLevel : public QDialog +{ + Q_OBJECT + +public: + explicit DialogCameraLevel(QWidget *parent = nullptr); + ~DialogCameraLevel(); + + // Delete copy constructor and assignment operator due to atomic members + DialogCameraLevel(const DialogCameraLevel&) = delete; + DialogCameraLevel& operator=(const DialogCameraLevel&) = delete; + + // 设置相机列表和presenter + void setCameraList(const std::vector>& cameraList, + LapWeldPresenter* presenter); + +private slots: + void on_btn_apply_clicked(); + void on_btn_cancel_clicked(); + void on_combo_camera_currentIndexChanged(int index); + +private: + Ui::DialogCameraLevel *ui; + + // 相机列表和名称 + std::vector> m_cameraList; + LapWeldPresenter* m_presenter = nullptr; + + // 扫描数据缓存 + std::vector m_scanDataCache; + std::mutex m_scanDataMutex; + + // 状态回调相关 + std::atomic m_swingFinished = false; // 摆动完成标志,同时表示扫描完成 + std::atomic m_callbackRestored = false; + + // 初始化相机选择框 + void initializeCameraCombo(); + + // 执行相机调平 + bool performCameraLeveling(); + + // 直接使用相机接口进行扫描 + bool startCameraScan(int cameraIndex); + bool stopCameraScan(int cameraIndex); + + // 检测数据回调函数 + static void StaticDetectionCallback(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint, void* pUserData); + void DetectionCallback(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint); + + // 状态回调函数 + static void StaticStatusCallback(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pInfoParam); + void StatusCallback(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pInfoParam); + + // 设置和恢复状态回调 + void setLevelingStatusCallback(); + void restorePresenterStatusCallback(); + + // 处理扫描到的地面数据进行调平计算 + bool calculatePlaneCalibration(double planeCalib[9], double& planeHeight, double invRMatrix[9]); + + // 清空扫描数据缓存 + void clearScanDataCache(); + + // Debug模式方法 + QString selectDebugDataFile(); + bool loadDebugDataAndSimulateScan(const QString& filePath); + void simulateScanProcess(); + + // 更新调平结果显示 + void updateLevelingResults(double planeCalib[9], double planeHeight, double invRMatrix[9]); + + // 保存调平结果到配置 + bool saveLevelingResults(double planeCalib[9], double planeHeight, double invRMatrix[9], int cameraIndex, const QString& cameraName); + + // 加载相机标定数据 + bool loadCameraCalibrationData(int cameraIndex, const QString& cameraName, + double planeCalib[9], double& planeHeight, double invRMatrix[9]); + + // 检查并显示相机标定状态 + void checkAndDisplayCalibrationStatus(int cameraIndex); +}; + +#endif // DialogCameraLevel_H diff --git a/App/LapWeld/LapWeldApp/dialogcameralevel.ui b/App/LapWeld/LapWeldApp/dialogcameralevel.ui new file mode 100644 index 0000000..f5d74d1 --- /dev/null +++ b/App/LapWeld/LapWeldApp/dialogcameralevel.ui @@ -0,0 +1,155 @@ + + + DialogCameraLevel + + + + 0 + 0 + 659 + 400 + + + + Form + + + background-color: rgb(25, 26, 28); + + + + + 20 + 100 + 111 + 31 + + + + + 18 + + + + color: rgb(221, 225, 233); + + + 调平结果: + + + + + + 20 + 10 + 621 + 45 + + + + + 18 + + + + color: rgb(221, 225, 233); + + + 相机调平 + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + + + + + + 180 + 350 + 101 + 38 + + + + + 18 + + + + image: url(:/resource/dialog_ok.png); + + + + + + + + + 390 + 350 + 111 + 38 + + + + + 18 + + + + image: url(:/resource/dialog_cancel.png); + + + + + + + + + 140 + 50 + 151 + 41 + + + + + 16 + + + + color: rgb(221, 225, 233); +background-color: rgb(47, 48, 52); + + + + + + 140 + 100 + 401 + 241 + + + + + 14 + + + + color: rgb(221, 225, 233); +background-color: rgb(47, 48, 52); +border: 1px solid #3B3D47; +padding: 5px; + + + + + + Qt::AlignmentFlag::AlignCenter + + + + + + diff --git a/App/LapWeld/LapWeldApp/main.cpp b/App/LapWeld/LapWeldApp/main.cpp new file mode 100644 index 0000000..31852a4 --- /dev/null +++ b/App/LapWeld/LapWeldApp/main.cpp @@ -0,0 +1,66 @@ +#include "mainwindow.h" + +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + // 设置应用程序图标 + a.setWindowIcon(QIcon(":/resource/logo.png")); + + // 使用应用程序名称作为唯一标识符 + const QString appKey = "LapWeldApp_SingleInstance_Key"; + + // 创建系统信号量,用于同步访问共享内存 + QSystemSemaphore semaphore(appKey + "_semaphore", 1); + semaphore.acquire(); // 获取信号量 + + // 创建共享内存对象 + QSharedMemory sharedMemory(appKey + "_memory"); + + bool isRunning = false; + + // 尝试附加到现有的共享内存 + if (sharedMemory.attach()) { + // 如果能够附加,说明已有实例在运行 + isRunning = true; + } else { + // 尝试创建新的共享内存 + if (!sharedMemory.create(1)) { + // 创建失败,可能是因为已经存在 + qDebug() << "Unable to create shared memory segment:" << sharedMemory.errorString(); + isRunning = true; + } + } + + semaphore.release(); // 释放信号量 + + if (isRunning) { + // 已有实例在运行,显示提示信息并退出 + QMessageBox::information(nullptr, + QObject::tr("应用程序已运行"), + QObject::tr("编织袋拆垛应用程序已经在运行中,请勿重复启动!"), + QMessageBox::Ok); + return 0; + } + + // 没有其他实例运行,正常启动应用程序 + MainWindow w; + w.show(); + + int result = a.exec(); + + // 应用程序退出时,清理共享内存 + if (sharedMemory.isAttached()) { + sharedMemory.detach(); + } + + return result; +} \ No newline at end of file diff --git a/App/LapWeld/LapWeldApp/mainwindow.cpp b/App/LapWeld/LapWeldApp/mainwindow.cpp new file mode 100644 index 0000000..ade3cf0 --- /dev/null +++ b/App/LapWeld/LapWeldApp/mainwindow.cpp @@ -0,0 +1,916 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include "resultitem.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "VrLog.h" +#include +#include +#include +#include +#include "Version.h" +#include "VrSimpleLog.h" +#include +#include +#include +#include +#include +#include "LapWeldPresenter.h" + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) + , m_selectedButton(nullptr) + , m_contextMenu(nullptr) + , m_saveDataAction(nullptr) +{ + ui->setupUi(this); + + // 设置窗口图标 + this->setWindowIcon(QIcon(":/resource/logo.png")); + + // 设置状态栏字体 + QFont statusFont = statusBar()->font(); + statusFont.setPointSize(12); + statusBar()->setFont(statusFont); + + // 设置状态栏颜色和padding + statusBar()->setStyleSheet("QStatusBar { color: rgb(239, 241, 245); padding: 20px; }"); + + // 在状态栏右侧添加版本信息(包含编译时间) + // 使用VrSimpleLog.h中的宏定义构建编译时间 + QString versionWithBuildTime = QString("%1_%2%3%4%5%6%7") + .arg(GetLapWeldFullVersion()) + .arg(YEAR) + .arg(MONTH, 2, 10, QChar('0')) + .arg(DAY, 2, 10, QChar('0')) + .arg(HOUR, 2, 10, QChar('0')) + .arg(MINUTE, 2, 10, QChar('0')) + .arg(SECOND, 2, 10, QChar('0')); + + QLabel* buildLabel = new QLabel(versionWithBuildTime); + buildLabel->setStyleSheet("color: rgb(239, 241, 245); font-size: 20px; margin-right: 16px;"); + statusBar()->addPermanentWidget(buildLabel); + + // 隐藏标题栏 + setWindowFlags(Qt::FramelessWindowHint); + + // 启动后自动最大化显示 + this->showMaximized(); + + // 初始化时隐藏label_work + ui->label_work->setVisible(false); + ui->detect_image_2->setVisible(false); + + // 初始化GraphicsScene + QGraphicsScene* scene = new QGraphicsScene(this); + ui->detect_image->setScene(scene); + + // 为第二个图像视图初始化GraphicsScene + QGraphicsScene* scene2 = new QGraphicsScene(this); + ui->detect_image_2->setScene(scene2); + + // 初始化日志模型 + m_logModel = new QStringListModel(this); + ui->detect_log->setModel(m_logModel); + + // 注册自定义类型,使其能够在信号槽中跨线程传递 + qRegisterMetaType("DetectionResult"); + qRegisterMetaType("WorkStatus"); + qRegisterMetaType("TargetPosition"); + + // 连接工作状态更新信号槽 + connect(this, &MainWindow::workStatusUpdateRequested, this, &MainWindow::updateWorkStatusLabel); + + // 连接检测结果更新信号槽 + connect(this, &MainWindow::detectionResultUpdateRequested, this, &MainWindow::updateDetectionResultDisplay); + + // 连接日志更新信号槽 + connect(this, &MainWindow::logUpdateRequested, this, &MainWindow::updateDetectionLog); + + // 连接清空日志信号槽 + connect(this, &MainWindow::logClearRequested, this, &MainWindow::clearDetectionLogUI); + + // 初始化右键菜单 + setupContextMenu(); + + updateStatusLog(tr("设备开始初始化...")); + + + // 初始化模块 + Init(); +} + +MainWindow::~MainWindow() +{ + // 释放业务逻辑处理类 + if (m_presenter) { + delete m_presenter; + m_presenter = nullptr; + } + + delete ui; +} + +void MainWindow::updateStatusLog(const QString& message) +{ + // 更新状态栏 + // statusBar()->showMessage(message); + + // 通过信号槽机制更新detect_log控件 + emit logUpdateRequested(message); +} + +void MainWindow::clearDetectionLog() +{ + // 通过信号槽机制清空日志 + emit logClearRequested(); +} + +void MainWindow::Init() +{ + // 创建业务逻辑处理类 + m_presenter = new LapWeldPresenter(); + + // 设置状态回调接口 + m_presenter->SetStatusCallback(this); + + m_deviceStatusWidget = new devstatus(); //因为初始化回调的数据要存储,所以要在init前创建好 + + // 连接devstatus的相机点击信号到MainWindow的槽函数 + connect(m_deviceStatusWidget, SIGNAL(cameraClicked(int)), + this, SLOT(onCameraClicked(int))); + + // 将设备状态widget添加到frame_dev中 + QVBoxLayout* frameDevLayout = new QVBoxLayout(ui->frame_dev); + frameDevLayout->setContentsMargins(0, 0, 0, 0); + frameDevLayout->addWidget(m_deviceStatusWidget); + + + // 设置列表视图模式 + ui->detect_result_list->setViewMode(QListView::IconMode); + ui->detect_result_list->setResizeMode(QListView::Adjust); + ui->detect_result_list->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui->detect_result_list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + +#if 0 + // 完全禁用拖拽和移动功能 + ui->detect_result_list->setDragDropMode(QAbstractItemView::NoDragDrop); + ui->detect_result_list->setDragEnabled(false); + ui->detect_result_list->setAcceptDrops(false); + ui->detect_result_list->setDefaultDropAction(Qt::IgnoreAction); + ui->detect_result_list->setMovement(QListView::Static); // 禁止项目移动 + + // 设置选择模式但禁用多选 + ui->detect_result_list->setSelectionMode(QAbstractItemView::SingleSelection); + ui->detect_result_list->setSelectionBehavior(QAbstractItemView::SelectItems); + + // 确保滚动功能正常工作 + ui->detect_result_list->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + ui->detect_result_list->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); + +#endif + + // 初始化期间禁用所有功能按钮 + setButtonsEnabled(false); + + // 在线程中执行初始化业务逻辑 + std::thread initThread([this]() { + updateStatusLog(tr("正在初始化系统...")); + + int result = m_presenter->Init(); + if (result != 0) { + updateStatusLog(tr("初始化失败,错误码:%1").arg(result)); + } else { + updateStatusLog(tr("系统初始化完成")); + } + }); + + // 分离线程,让其在后台运行 + initThread.detach(); +} + +void MainWindow::displayImage(const QImage& image) +{ + if (image.isNull()) { + updateStatusLog(tr("图片无效")); + return; + } + + QGraphicsScene* scene = ui->detect_image->scene(); + scene->clear(); + + QPixmap pixmap = QPixmap::fromImage(image); + scene->addPixmap(pixmap); + ui->detect_image->fitInView(scene->sceneRect(), Qt::KeepAspectRatio); +} + +// 在第二个图像视图中显示图像 +void MainWindow::displayImageInSecondView(const QImage& image) +{ + if (image.isNull()) { + updateStatusLog(tr("第二相机图片无效")); + return; + } + + QGraphicsScene* scene2 = ui->detect_image_2->scene(); + scene2->clear(); + + QPixmap pixmap = QPixmap::fromImage(image); + scene2->addPixmap(pixmap); + ui->detect_image_2->fitInView(scene2->sceneRect(), Qt::KeepAspectRatio); +} + +// 添加扩展版本的检测结果函数 +void MainWindow::addDetectionResult(const DetectionResult& result) +{ + // 清空之前的所有检测结果数据 + ui->detect_result_list->clear(); + + // 设置item间距和流向(gridSize现在在布局调整函数中动态设置) + int itemSpacing = 0; + ui->detect_result_list->setSpacing(itemSpacing); // 设置item间距为0px + + // 获取当前的gridSize(由布局调整函数设置) + QSize currentGridSize = ui->detect_result_list->gridSize(); + int gridWidth = currentGridSize.width(); + int gridHeight = currentGridSize.height(); + + int targetIndex = 1; + for (const auto& position : result.positions) { + // 创建自定义的ResultItem widget + ResultItem* resultWidget = new ResultItem(); + + // 设置widget的autoFillBackground属性,确保背景色生效 + resultWidget->setAutoFillBackground(true); + + // 设置检测结果数据,直接使用TargetPosition + resultWidget->setResultData(targetIndex, position); + + // 创建QListWidgetItem + QListWidgetItem* item = new QListWidgetItem(); + + // 设置item背景为透明,让自定义widget的背景显示出来 + item->setBackground(QBrush(Qt::transparent)); + + // 设置item的大小提示 - 应该与gridSize一致 + item->setSizeHint(QSize(gridWidth, gridHeight)); + + // 确保每个item都不能被拖动 + item->setFlags(item->flags() & ~Qt::ItemIsDragEnabled & ~Qt::ItemIsDropEnabled); + + // 将item添加到列表 + ui->detect_result_list->addItem(item); + + // 将自定义widget设置为item的显示内容 + ui->detect_result_list->setItemWidget(item, resultWidget); + + targetIndex++; + } +} + +// 状态更新槽函数 +void MainWindow::OnStatusUpdate(const std::string& statusMessage) +{ + LOG_DEBUG("[UI Display] Status update: %s\n", statusMessage.c_str()); + updateStatusLog(QString::fromStdString(statusMessage)); +} + +void MainWindow::OnDetectionResult(const DetectionResult& result) +{ + // 通过信号槽机制更新UI(确保在主线程中执行) + emit detectionResultUpdateRequested(result); +} + +void MainWindow::OnCamera1StatusChanged(bool isConnected) +{ + // 直接更新设备状态widget + if (m_deviceStatusWidget) { + m_deviceStatusWidget->updateCamera1Status(isConnected); + } +} + +// 相机2状态更新槽函数 +void MainWindow::OnCamera2StatusChanged(bool isConnected) +{ + // 直接更新设备状态widget + if (m_deviceStatusWidget) { + m_deviceStatusWidget->updateCamera2Status(isConnected); + } +} + +// 相机个数更新槽函数 +void MainWindow::OnCameraCountChanged(int cameraCount) +{ + // 设置设备状态widget中的相机数量 + if (m_deviceStatusWidget) { + m_deviceStatusWidget->setCameraCount(cameraCount); + + // 设置相机名称(从配置文件中读取) + if (m_presenter) { + const auto& cameraList = m_presenter->GetCameraList(); + if (cameraList.size() >= 1) { + QString camera1Name = QString::fromStdString(cameraList[0].first); + m_deviceStatusWidget->setCamera1Name(camera1Name); + } + if (cameraList.size() >= 2) { + QString camera2Name = QString::fromStdString(cameraList[1].first); + m_deviceStatusWidget->setCamera2Name(camera2Name); + } + } + } + + // 根据相机个数调整页面布局 + adjustLayoutForCameraCount(cameraCount); + + // 如果只有一个相机,更新状态消息 + if (cameraCount < 2) { + // 更新状态消息 + updateStatusLog(tr("系统使用单相机模式")); + } else { + // 更新状态消息 + updateStatusLog(tr("系统使用双相机模式")); + } +} + +// 机械臂状态更新槽函数 +void MainWindow::OnRobotConnectionChanged(bool isConnected) +{ + // 直接更新设备状态widget + if (m_deviceStatusWidget) { + m_deviceStatusWidget->updateRobotStatus(isConnected); + } +} + +// 串口连接状态更新槽函数 +void MainWindow::OnSerialConnectionChanged(bool isConnected) +{ + // 直接更新设备状态widget + if (m_deviceStatusWidget) { + m_deviceStatusWidget->updateSerialStatus(isConnected); + } +} + +// 工作状态更新槽函数 +void MainWindow::OnWorkStatusChanged(WorkStatus status) +{ + // 通过信号槽机制更新UI(确保在主线程中执行) + emit workStatusUpdateRequested(status); +} + +void MainWindow::updateWorkStatusLabel(WorkStatus status) +{ + // 如果状态变为Working,清空检测日志(表示开始新的检测) + if (status == WorkStatus::Working) { + clearDetectionLog(); + } + + // 获取状态对应的显示文本 + QString statusText = QString::fromStdString(WorkStatusToString(status)); + + + // 在label_work中显示状态 + if (!ui->label_work) return; + + ui->label_work->setText(statusText); + + statusText = "【工作状态】" + statusText; + updateStatusLog(statusText); + + // 根据不同状态设置不同的样式和按钮启用状态 + switch (status) { + case WorkStatus::Ready: + ui->label_work->setStyleSheet("color: green;"); + setButtonsEnabled(true); // 就绪状态下启用所有按钮 + break; + case WorkStatus::InitIng: + ui->label_work->setStyleSheet("color: blue;"); + setButtonsEnabled(false); // 初始化期间禁用按钮 + break; + case WorkStatus::Working: + ui->label_work->setStyleSheet("color: blue;"); + setButtonsEnabled(false); // 工作期间禁用按钮 + break; + case WorkStatus::Completed: + ui->label_work->setStyleSheet("color: green; font-weight: bold;"); + setButtonsEnabled(true); // 完成后启用按钮 + break; + case WorkStatus::Error: + ui->label_work->setStyleSheet("color: red; font-weight: bold;"); + setButtonsEnabled(true); // 错误状态下仍可操作 + break; + default: + ui->label_work->setStyleSheet(""); + setButtonsEnabled(false); // 未知状态禁用按钮 + break; + } +} + +void MainWindow::updateDetectionResultDisplay(const DetectionResult& result) +{ + // 根据相机索引决定显示在哪个图像视图上 + if (result.cameraIndex == 2) { + // 第二个相机的结果显示在detect_image_2上 + updateDetectionLog(tr("相机2检测结果更新")); + displayImageInSecondView(result.image); + } else { + // 第一个相机或默认显示在detect_image上 + updateDetectionLog(tr("相机1检测结果更新")); + displayImage(result.image); + } + + // 更新检测结果到列表 + addDetectionResult(result); +} + +void MainWindow::updateDetectionLog(const QString& message) +{ + // 在UI线程中更新detect_log控件(QListView) + if (!m_logModel) return ; + + // 添加时间戳(包含年月日) + // QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); + QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss"); + QString logEntry = QString("[%1] %2").arg(timestamp).arg(message); + + // 获取当前数据 + QStringList logList = m_logModel->stringList(); + + // 添加新的日志条目 + logList.append(logEntry); + + // 更新模型 + m_logModel->setStringList(logList); + + // 自动滚动到最底部 + QModelIndex lastIndex = m_logModel->index(logList.size() - 1); + ui->detect_log->scrollTo(lastIndex); +} + +void MainWindow::clearDetectionLogUI() +{ + // 在UI线程中清空检测日志 + if (m_logModel) { + m_logModel->setStringList(QStringList()); + } +} + +void MainWindow::on_btn_start_clicked() +{ + // 检查Presenter是否已初始化 + if (!m_presenter) { + updateStatusLog(tr("系统未初始化,请等待初始化完成")); + return; + } + + // 清空检测日志,开始新的检测 + clearDetectionLog(); + + // 使用Presenter启动检测 + m_presenter->StartDetection(-1, false); +} + +void MainWindow::on_btn_stop_clicked() +{ + // 检查Presenter是否已初始化 + if (!m_presenter) { + updateStatusLog(tr("系统未初始化,请等待初始化完成")); + return; + } + + m_presenter->StopDetection(); +} + +void MainWindow::on_btn_camera_clicked() +{ + // 检查是否有其他按钮已被选中 + if (m_selectedButton != nullptr && m_selectedButton != ui->btn_camera) { + updateStatusLog(tr("请先关闭当前打开的配置窗口")); + return; + } + + // 检查Presenter是否已初始化 + if (!m_presenter) { + updateStatusLog(tr("系统未初始化,请等待初始化完成")); + return; + } + + // 设置当前按钮为选中状态 + setButtonSelectedState(ui->btn_camera, true); + + // 使用新的相机列表获取功能 + + // 如果对话框不存在,创建新的DialogCamera + if (nullptr == ui_dialogCamera) { + ui_dialogCamera = new DialogCamera(m_presenter->GetCameraList(), this); + + // 连接对话框关闭信号 + connect(ui_dialogCamera, &QDialog::finished, this, [this]() { + setButtonSelectedState(ui->btn_camera, false); + }); + } + + ui_dialogCamera->show(); +} + +void MainWindow::on_btn_camera_levelling_clicked() +{ + // 检查是否有其他按钮已被选中 + if (m_selectedButton != nullptr && m_selectedButton != ui->btn_camera_levelling) { + updateStatusLog(tr("请先关闭当前打开的配置窗口")); + return; + } + + // 检查Presenter是否已初始化 + if (!m_presenter) { + updateStatusLog(tr("系统未初始化,请等待初始化完成")); + return; + } + + // 设置当前按钮为选中状态 + setButtonSelectedState(ui->btn_camera_levelling, true); + + if(nullptr == ui_dialogCameraLevel){ + ui_dialogCameraLevel = new DialogCameraLevel(this); + + // 连接对话框关闭信号 + connect(ui_dialogCameraLevel, &QDialog::finished, this, [this]() { + setButtonSelectedState(ui->btn_camera_levelling, false); + }); + } + + // 设置相机列表和presenter到对话框 + ui_dialogCameraLevel->setCameraList(m_presenter->GetCameraList(), m_presenter); + + ui_dialogCameraLevel->show(); +} + +void MainWindow::on_btn_hide_clicked() +{ + // 最小化窗口 + this->showMinimized(); +} + +void MainWindow::on_btn_close_clicked() +{ + // 关闭应用程序 + this->close(); +} + +void MainWindow::on_btn_test_clicked() +{ + // 检查是否有其他按钮已被选中 + if (m_selectedButton != nullptr && m_selectedButton != ui->btn_test) { + updateStatusLog(tr("请先关闭当前打开的配置窗口")); + return; + } + + + // 设置当前按钮为选中状态 + setButtonSelectedState(ui->btn_test, true); + + // 打开文件选择对话框 + QString fileName = QFileDialog::getOpenFileName( + this, + tr("选择调试数据文件"), + QString(), + tr("激光数据文件 (*.txt);;所有文件 (*.*)") + ); + + if (fileName.isEmpty()) { + // 用户取消了文件选择,恢复按钮状态 + setButtonSelectedState(ui->btn_test, false); + return; + } + + // 检查Presenter是否已初始化 + if (!m_presenter) { + // 恢复按钮状态 + setButtonSelectedState(ui->btn_test, false); + QMessageBox::warning(this, tr("错误"), tr("系统未正确初始化!")); + return; + } + + // 清空检测日志,开始新的检测 + clearDetectionLog(); + + std::thread t([this, fileName]() { + int result = m_presenter->LoadDebugDataAndDetect(fileName.toStdString()); + + if (result == 0) { + updateStatusLog(tr("调试数据加载和检测成功")); + } else { + QString errorMsg = tr("调试数据复检失败: %1").arg(result); + updateStatusLog(errorMsg); + } + + // 测试完成后恢复按钮状态 + QMetaObject::invokeMethod(this, [this]() { + setButtonSelectedState(ui->btn_test, false); + }, Qt::QueuedConnection); + }); + t.detach(); +} + +// 设置按钮启用/禁用状态 +void MainWindow::setButtonsEnabled(bool enabled) +{ + // 功能按钮 + if (ui->btn_start) ui->btn_start->setEnabled(enabled); + if (ui->btn_stop) ui->btn_stop->setEnabled(enabled); + if (ui->btn_camera) ui->btn_camera->setEnabled(enabled); + if (ui->btn_algo_config) ui->btn_algo_config->setEnabled(enabled); + if (ui->btn_camera_levelling) ui->btn_camera_levelling->setEnabled(enabled); + // if (ui->btn_test) ui->btn_test->setEnabled(enabled); + + // 窗口控制按钮(最小化和关闭)始终可用 + // if (ui->btn_hide) ui->btn_hide->setEnabled(true); + // if (ui->btn_close) ui->btn_close->setEnabled(true); +} + +// 设置按钮图像 +void MainWindow::setButtonImage(QPushButton* button, const QString& imagePath) +{ + if (button) { + QString styleSheet = QString("image: url(%1);").arg(imagePath); + button->setStyleSheet(styleSheet); + } +} + +// 设置按钮选中状态 +void MainWindow::setButtonSelectedState(QPushButton* button, bool selected) +{ + if (!button) return; + + QString normalImage, selectedImage; + + // 根据按钮确定对应的图像路径 + if (button == ui->btn_camera) { + normalImage = ":/resource/config_camera.png"; + selectedImage = ":/resource/config_camera_s.png"; + } else if (button == ui->btn_algo_config) { + normalImage = ":/resource/config_algo.png"; + selectedImage = ":/resource/config_algo_s.png"; + } else if (button == ui->btn_camera_levelling) { + normalImage = ":/resource/config_camera_level.png"; + selectedImage = ":/resource/config_camera_level_s.png"; + } else if (button == ui->btn_test) { + normalImage = ":/resource/config_data_test.png"; + selectedImage = ":/resource/config_data_test_s.png"; + } + + // 设置对应的图像 + if (selected) { + setButtonImage(button, selectedImage); + m_selectedButton = button; + // 禁用其他按钮 + setOtherButtonsEnabled(button, false); + } else { + setButtonImage(button, normalImage); + if (m_selectedButton == button) { + m_selectedButton = nullptr; + // 启用所有按钮 + setOtherButtonsEnabled(nullptr, true); + } + } +} + +// 恢复所有按钮状态 +void MainWindow::restoreAllButtonStates() +{ + setButtonSelectedState(ui->btn_camera, false); + setButtonSelectedState(ui->btn_algo_config, false); + setButtonSelectedState(ui->btn_camera_levelling, false); + setButtonSelectedState(ui->btn_test, false); +} + +// 设置其他按钮的启用状态(用于互斥控制) +void MainWindow::setOtherButtonsEnabled(QPushButton* exceptButton, bool enabled) +{ + QList configButtons = { + ui->btn_camera, + ui->btn_algo_config, + ui->btn_camera_levelling, + ui->btn_test + }; + + for (QPushButton* button : configButtons) { + if (button && button != exceptButton) { + button->setEnabled(enabled); + } + } +} + +// 根据相机个数调整页面布局 +void MainWindow::adjustLayoutForCameraCount(int cameraCount) +{ + if (cameraCount <= 1) { + // 单相机模式布局 + setupSingleCameraLayout(); + } else { + // 多相机模式布局 + setupMultiCameraLayout(); + } +} + +// 设置单相机模式布局 +void MainWindow::setupSingleCameraLayout() +{ + // 单相机模式下的原始布局 + // detect_image: 占据左侧大部分空间 + ui->detect_image->setGeometry(20, 140, 1311, 890); + + // detect_image_2: 隐藏 + ui->detect_image_2->setVisible(false); + + // 设备状态框架: 位于右上方,恢复原始尺寸 + ui->frame_dev->setGeometry(1344, 140, 556, 64); + + // 检测结果列表: 位于右侧中间 + ui->detect_result_list->setGeometry(1344, 216, 556, 622); + + // 单相机模式下恢复原有的垂直滚动设置 + ui->detect_result_list->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui->detect_result_list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui->detect_result_list->setFlow(QListView::LeftToRight); // 从左到右排列 + ui->detect_result_list->setWrapping(true); // 允许换行 + + // 单相机模式:设置gridSize,gridHeight = 207 + int gridWidth = 275; + int gridHeight = 206; + ui->detect_result_list->setGridSize(QSize(gridWidth, gridHeight)); + + // 恢复设备状态widget的原始布局 + m_deviceStatusWidget->setCameraCount(1); + + + // 日志: 位于右下方 + ui->detect_log->setGeometry(1344, 850, 556, 176); + + updateStatusLog(tr("页面布局已调整为单相机模式")); +} + +// 设置多相机模式布局 +void MainWindow::setupMultiCameraLayout() +{ + // 多相机模式下的布局调整 + // detect_image: 缩小到左侧一半空间 + ui->detect_image->setGeometry(10, 140, 940, 660); + + // detect_image_2: 显示在右侧一半空间 + ui->detect_image_2->setVisible(true); + ui->detect_image_2->setGeometry(970, 140, 940, 660); + + // 设备状态框架: 移动到下方左侧,调整为更高的尺寸以适应纵向排布 + ui->frame_dev->setGeometry(10, 806, 180, 200); + + // 检测结果列表: 移动到下方中间 + ui->detect_result_list->setGeometry(196, 806, 1380, 200); + + + // 多相机模式:设置gridSize,gridHeight = 200 + int gridWidth = 230; + int gridHeight = 190; + ui->detect_result_list->setGridSize(QSize(gridWidth, gridHeight)); + + m_deviceStatusWidget->setCameraCount(2); + + // 日志: 移动到下方右侧 + ui->detect_log->setGeometry(1582, 806, 328, 200); + + updateStatusLog(tr("页面布局已调整为多相机模式")); +} + +// 处理相机点击事件 +void MainWindow::onCameraClicked(int cameraIndex) +{ + if (m_presenter) { + m_presenter->SetDefaultCameraIndex(cameraIndex); + } +} + +// 设置右键菜单 +void MainWindow::setupContextMenu() +{ + // 创建右键菜单 + m_contextMenu = new QMenu(this); + + // 创建保存数据动作 + m_saveDataAction = new QAction(tr("保存检测数据"), this); + m_saveDataAction->setIcon(QIcon(":/resource/save.png")); // 如果有保存图标的话 + + // 设置右键菜单的样式表,使菜单项默认显示为灰色 + m_contextMenu->setStyleSheet( + "QMenu::item { color: gray; }" + "QMenu::item:enabled { color: gray; }" + "QMenu::item:disabled { color: gray; }" + ); + + // 添加动作到菜单 + m_contextMenu->addAction(m_saveDataAction); + + // 连接信号槽 + connect(m_saveDataAction, &QAction::triggered, this, &MainWindow::onSaveDetectionData); + + // 为两个图像视图设置右键菜单事件 + ui->detect_image->setContextMenuPolicy(Qt::CustomContextMenu); + ui->detect_image_2->setContextMenuPolicy(Qt::CustomContextMenu); + + // 连接右键菜单信号 + connect(ui->detect_image, &QGraphicsView::customContextMenuRequested, + this, [this](const QPoint& pos) { showContextMenu(pos, ui->detect_image); }); + connect(ui->detect_image_2, &QGraphicsView::customContextMenuRequested, + this, [this](const QPoint& pos) { showContextMenu(pos, ui->detect_image_2); }); +} + +// 显示右键菜单 +void MainWindow::showContextMenu(const QPoint& pos, QGraphicsView* view) +{ + // 检查是否有检测数据可以保存 + if (!m_presenter) { + updateStatusLog(tr("系统未初始化,无法保存数据")); + return; + } + + // 根据视图确定相机索引 + int cameraIndex = (view == ui->detect_image_2) ? 2 : 1; + + // 检查是否是上次检测的相机 + if (cameraIndex != m_presenter->GetDetectIndex()) { + updateStatusLog(tr("当前视图不是上次检测的相机,无法保存数据")); + return; + } + + if (m_presenter->GetDetectionDataCacheSize() == 0) { + updateStatusLog(tr("没有检测数据可以保存")); + return; + } + + // 显示右键菜单 + m_contextMenu->exec(view->mapToGlobal(pos)); +} + +// 保存检测数据槽函数 +void MainWindow::onSaveDetectionData() +{ + if (!m_presenter) { + updateStatusLog(tr("系统未初始化,无法保存数据")); + return; + } + + // 让用户选择保存目录 + QString dirPath = QFileDialog::getExistingDirectory(this, + tr("选择保存目录"), + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); + + if (dirPath.isEmpty()) { + updateStatusLog(tr("用户取消了保存操作")); + return; + } + + // 生成文件名(包含时间戳和相机信息) + std::string timeStamp = CVrDateUtils::GetNowTime(); + std::string fileName = "Laserline_" + std::to_string(m_presenter->GetDetectIndex()) + "_" + timeStamp + ".txt"; + QString fullPath = QDir(dirPath).filePath(QString::fromStdString(fileName)); + + // 保存数据 + if (saveDetectionDataToFile(fullPath, m_presenter->GetDetectIndex())) { + updateStatusLog(tr("检测数据已保存到:%1").arg(fileName.c_str())); + } else { + updateStatusLog(tr("保存检测数据失败")); + } +} + +// 保存检测数据到文件 +bool MainWindow::saveDetectionDataToFile(const QString& filePath, int cameraIndex) +{ + try { + // 直接调用Presenter的保存方法 + int result = m_presenter->SaveDetectionDataToFile(filePath.toStdString()); + + if (result == 0) { + updateStatusLog(tr("检测数据保存成功")); + return true; + } else { + updateStatusLog(tr("保存数据失败,错误码:%1").arg(result)); + return false; + } + + } catch (const std::exception& e) { + updateStatusLog(tr("保存数据时发生异常:%1").arg(e.what())); + return false; + } +} + diff --git a/App/LapWeld/LapWeldApp/mainwindow.h b/App/LapWeld/LapWeldApp/mainwindow.h new file mode 100644 index 0000000..72f2651 --- /dev/null +++ b/App/LapWeld/LapWeldApp/mainwindow.h @@ -0,0 +1,139 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dialogcamera.h" +#include "dialogcameralevel.h" +#include "devstatus.h" +#include "LapWeldPresenter.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +class MainWindow : public QMainWindow, public IYLapWeldStatus +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + + void updateStatusLog(const QString& message); + void clearDetectionLog(); + void Init(); + + // 实现IYLapWeldStatus接口 + virtual void OnStatusUpdate(const std::string& statusMessage) override; + virtual void OnDetectionResult(const DetectionResult& result) override; + virtual void OnCamera1StatusChanged(bool isConnected) override; + virtual void OnCamera2StatusChanged(bool isConnected) override; + virtual void OnRobotConnectionChanged(bool isConnected) override; + virtual void OnSerialConnectionChanged(bool isConnected) override; + virtual void OnCameraCountChanged(int cameraCount) override; + virtual void OnWorkStatusChanged(WorkStatus status) override; + +signals: + // 工作状态更新信号 + void workStatusUpdateRequested(WorkStatus status); + + // 检测结果更新信号 + void detectionResultUpdateRequested(const DetectionResult& result); + + // 日志更新信号 + void logUpdateRequested(const QString& message); + + // 清空日志信号 + void logClearRequested(); + +private slots: + + // 工作状态更新槽函数 + void updateWorkStatusLabel(WorkStatus status); + + // 检测结果更新槽函数 + void updateDetectionResultDisplay(const DetectionResult& result); + + // 日志更新槽函数 + void updateDetectionLog(const QString& message); + + // 清空日志槽函数 + void clearDetectionLogUI(); + + // 处理相机点击事件 + void onCameraClicked(int cameraIndex); + + // UI操作相关槽 + void on_btn_start_clicked(); + void on_btn_stop_clicked(); + + void on_btn_camera_clicked(); + + void on_btn_camera_levelling_clicked(); + + void on_btn_hide_clicked(); + + void on_btn_close_clicked(); + + void on_btn_test_clicked(); + + // 右键菜单相关槽函数 + void onSaveDetectionData(); + +private: + Ui::MainWindow * ui; + DialogCamera* ui_dialogCamera = nullptr; + DialogCameraLevel* ui_dialogCameraLevel = nullptr; + + // 业务逻辑处理类 + LapWeldPresenter* m_presenter = nullptr; + + // 日志模型 + QStringListModel* m_logModel = nullptr; + + // 设备状态显示widget的引用 + devstatus* m_deviceStatusWidget = nullptr; + + // 当前选中的按钮(用于互斥控制) + QPushButton* m_selectedButton = nullptr; + + // 右键菜单相关 + QMenu* m_contextMenu = nullptr; + QAction* m_saveDataAction = nullptr; + + void displayImage(const QImage& image); + void displayImageInSecondView(const QImage& image); + + // 扩展版本的检测结果添加函数 + void addDetectionResult(const DetectionResult& result); + + // 设置按钮启用/禁用状态 + void setButtonsEnabled(bool enabled); + + // 按钮图像切换辅助函数 + void setButtonImage(QPushButton* button, const QString& imagePath); + void setButtonSelectedState(QPushButton* button, bool selected); + void restoreAllButtonStates(); + void setOtherButtonsEnabled(QPushButton* exceptButton, bool enabled); + + // 页面布局调整函数 + void adjustLayoutForCameraCount(int cameraCount); + void setupSingleCameraLayout(); + void setupMultiCameraLayout(); + + // 右键菜单相关函数 + void setupContextMenu(); + void showContextMenu(const QPoint& pos, QGraphicsView* view); + bool saveDetectionDataToFile(const QString& filePath, int cameraIndex); +}; +#endif // MAINWINDOW_H diff --git a/App/LapWeld/LapWeldApp/mainwindow.ui b/App/LapWeld/LapWeldApp/mainwindow.ui new file mode 100644 index 0000000..c5e63d6 --- /dev/null +++ b/App/LapWeld/LapWeldApp/mainwindow.ui @@ -0,0 +1,387 @@ + + + MainWindow + + + + 0 + 0 + 1920 + 1080 + + + + 自动拆包系统 + + + false + + + background-color:rgb(25, 26, 28) + + + + + + 20 + 140 + 1311 + 890 + + + + background-color: rgb(37, 38, 42); + + + + + + 1344 + 852 + 556 + 178 + + + + + 12 + + + + background-color: rgb(37, 38, 42); +color: rgb(239, 241, 245); + + + QAbstractItemView::NoEditTriggers + + + + + + 40 + 34 + 341 + 51 + + + + + 40 + + + + color: rgb(239, 241, 245); +background-color: rgba(255, 255, 255, 0); + + + 搭接焊接 + + + + + + 1344 + 216 + 556 + 624 + + + + background-color:rgb(37, 38, 42) + + + 0 + + + + + + 1218 + 21 + 220 + 80 + + + + + 18 + + + + image: url(:/resource/config_data_test.png); +background-color: rgb(38, 40, 47); +border: none; + + + + + + + + + 728 + 21 + 220 + 80 + + + + + 18 + + + + image: url(:/resource/config_algo.png); +background-color: rgb(38, 40, 47); +border: none; + + + + + + + + + 973 + 21 + 220 + 80 + + + + + 18 + + + + image: url(:/resource/config_camera_level.png); +background-color: rgb(38, 40, 47); +border: none; + + + + + + + + + 483 + 21 + 220 + 80 + + + + + 18 + + + + image: url(:/resource/config_camera.png); +background-color: rgb(38, 40, 47); +border: none; + + + + + + + + + 0 + 0 + 1920 + 120 + + + + background-color: rgb(38, 40, 47); + + + + + + + + 1450 + 20 + 311 + 81 + + + + + 24 + + + + color: rgb(255, 255, 255); + + + 工作状态 + + + Qt::AlignCenter + + + + + + 1841 + 32 + 56 + 56 + + + + background-image: url(:/resource/close.png); +background-color: rgba(255, 255, 255, 0); + + + + + + + + + 1762 + 32 + 56 + 56 + + + + background-image: url(:/resource/hide.png); +background-color: rgba(255, 255, 255, 0); + + + + + + + + + 1525 + 21 + 80 + 80 + + + + + 18 + + + + background-image: url(:/resource/start.png); +background-color: rgba(255, 255, 255, 0); + + + + + + + + + 1645 + 21 + 80 + 80 + + + + + 18 + + + + background-image: url(:/resource/stop.png); +background-color: rgba(255, 255, 255, 0); + + + + + + + + + + 1344 + 140 + 556 + 64 + + + + background-color: rgba(37, 38, 42, 0); + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + 966 + 140 + 934 + 678 + + + + background-color: rgb(37, 38, 42); + + + detect_image_2 + groupBox + detect_image + detect_log + label + detect_result_list + btn_test + btn_algo_config + btn_camera_levelling + btn_camera + frame_dev + + + + + 0 + 0 + 1920 + 19 + + + + color: rgb(37, 38, 42); + + + + + + 相机一配置 + + + + + 相机二配置 + + + + + 加载测试数据 + + + + 16 + + + + + + + diff --git a/App/LapWeld/LapWeldApp/resource/camera_offline.png b/App/LapWeld/LapWeldApp/resource/camera_offline.png new file mode 100644 index 0000000000000000000000000000000000000000..c12f7b867e193f1d50ff4a1762196914440b234c GIT binary patch literal 1239 zcmV;|1StE7P)-|iG&9eC24Hj*{!JD)F9ML+bwC}1vTkY2^vXAM7lspG!jYZ zlZnEMmr&Y9DN2k~tC;T27)TQ=Pb55%6vY(nWYet7Ieus8S-RV$b6sT3PSSMu%zyj- z+xgE~N-F%J6>dMN0{E{ZSdBn60s%b`N(z14(z0xYs=iJXrOA~&j7{ILtm8cwF8t$) zehH#b01Ro`e&{&h(U`tYT|elNXAyZ|04JK8mp4QrR|%!dJsHFK&DmUT)y~Puo1XZJ zhyw!{j>r2r=P5r%P)buBnT*%S6{rdXz_<6M2_ZzG0KkcL>uT$>*{4v9s*^|I^<_1b z?qZy8b)sGBy+Y}kTbv(5G@J{XG0Tibf89AgJ||@s06;PRgcr{SG3Xxf{*2;(ZV`Y~ zDy2Me_G}I>9KG+sNH$Nac;G2T%CRV&ZT+@+a|HIvYm z7fSw^nOUhsB2OYU+wgT8{vL1x2v*N2jJ;@OvlrF6y5pSEB>2h#z=E=YTsC`f`=v`W zOBwh1nl+7)Xmme3*XtNh?{=FNoN}3T(dmato z6!wn?@5Wd52*4v*i}OughLJ8dVx+b85ysdmv92h})%J;r--~GmW3hxv={Xo0bpT(U z8lZ~uW=A^x=|bhUX9;-^Ypn}X#KPwZRPv$XNDU_vZ*ayw@esXmY5?BtD&jwxuCMGuOA|`%e7CGC_}Jjmh*?EdSIfE>#NMN5 znrOKXx!?~T0fhT>QrBOR%0}Yxx6n~Oa-lp=g3k4JdnR*KE}%_*jkJ2%g^b_;pyUX) z^F2}-a{naSAp+vz;d~IaAT~Ao!5H%){=MTuLT~`c;RDFw)B*tK0AQmF<#$Lp9|M3* zasgv`3N_T{LPl@^6(52BLk(1R7ZQ0|>HC0~!|&xzEO%`&{)bY;cC#q+EOZTBX#i({ zQDT~6lO&|ResvA!TV6AaBXSf8bf9sXMsXOir%~p08m06LHapwXH8o{AwbeiXY~f*_ z2Vgu7F*}6hp?JIvQP>S5W40(fu=!Ee7C6X*qa&r%*YJ z@gs92k=TkGjnRT4rc|BxyuFN@j<)uNMeIjWIeF|00!mA_QO*yGi-CeRT25p9$}Y8$ zso2Pb=cUX*zYq8|EKI&CfH1x3M^-fge!LbY&%d;b=ue3JO9=n~002ovPDHLkV1l95 BM=JmT literal 0 HcmV?d00001 diff --git a/App/LapWeld/LapWeldApp/resource/camera_online.png b/App/LapWeld/LapWeldApp/resource/camera_online.png new file mode 100644 index 0000000000000000000000000000000000000000..57bad650fe9c12365ab46024417dea8c2eb54825 GIT binary patch literal 1240 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 Zs;~0k*So;-shbrJk-DmmN|}-!@;_8N#{&QW literal 0 HcmV?d00001 diff --git a/App/LapWeld/LapWeldApp/resource/config_algo_s.png b/App/LapWeld/LapWeldApp/resource/config_algo_s.png new file mode 100644 index 0000000000000000000000000000000000000000..e97a5b57bc0568bd412d613a574af96f671595bc GIT binary patch literal 4388 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 E0Bkf|QUCw| literal 0 HcmV?d00001 diff --git a/App/LapWeld/LapWeldApp/resource/config_camera.png b/App/LapWeld/LapWeldApp/resource/config_camera.png new file mode 100644 index 0000000000000000000000000000000000000000..906a4421385df7a0ce5865ea3839e92de721db13 GIT binary patch literal 4780 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 literal 0 HcmV?d00001 diff --git a/App/LapWeld/LapWeldApp/resource/config_camera_level.png b/App/LapWeld/LapWeldApp/resource/config_camera_level.png new file mode 100644 index 0000000000000000000000000000000000000000..a28509139d5c06c8c1b76cc86ddbdae0cda1098b GIT binary patch literal 4370 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 literal 0 HcmV?d00001 diff --git a/App/LapWeld/LapWeldApp/resource/config_camera_level_s.png b/App/LapWeld/LapWeldApp/resource/config_camera_level_s.png new file mode 100644 index 0000000000000000000000000000000000000000..aba0174bcc0084960cce6cd165d318b9149b92d3 GIT binary patch literal 4348 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 literal 0 HcmV?d00001 diff --git a/App/LapWeld/LapWeldApp/resource/config_data_test.png b/App/LapWeld/LapWeldApp/resource/config_data_test.png new file mode 100644 index 0000000000000000000000000000000000000000..d90e55abf74c1f85ae6fbccf19e7c2bf6f172631 GIT binary patch literal 5672 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 S0zVO83?(@=*=lL?u>S$a``Um2 literal 0 HcmV?d00001 diff --git a/App/LapWeld/LapWeldApp/resource/config_data_test.svg b/App/LapWeld/LapWeldApp/resource/config_data_test.svg new file mode 100644 index 0000000..0fdf618 --- /dev/null +++ b/App/LapWeld/LapWeldApp/resource/config_data_test.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/App/LapWeld/LapWeldApp/resource/config_data_test_s.png b/App/LapWeld/LapWeldApp/resource/config_data_test_s.png new file mode 100644 index 0000000000000000000000000000000000000000..37b831ee63cb4db42026830c707cabc412da694a GIT binary patch literal 5541 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 literal 0 HcmV?d00001 diff --git a/App/LapWeld/LapWeldApp/resource/config_model.png b/App/LapWeld/LapWeldApp/resource/config_model.png new file mode 100644 index 0000000000000000000000000000000000000000..d62e017741468220113867548ef95a5ea6212691 GIT binary patch literal 3500 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 KMNUMnLSTYIzVE~U literal 0 HcmV?d00001 diff --git a/App/LapWeld/LapWeldApp/resource/hide.png b/App/LapWeld/LapWeldApp/resource/hide.png new file mode 100644 index 0000000000000000000000000000000000000000..d22235f0962d459ab1a02f07dde609137dfc82ee GIT binary patch literal 312 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 J=vzXc^Z%S^69E7K literal 0 HcmV?d00001 diff --git a/App/LapWeld/LapWeldApp/resource/logo.png b/App/LapWeld/LapWeldApp/resource/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..073c220ff51b2c5881d4866d3dc9c3eab238ac8c GIT binary patch literal 7080 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 JPDHLkV1kzG>O}wm literal 0 HcmV?d00001 diff --git a/App/LapWeld/LapWeldApp/resource/stop.png b/App/LapWeld/LapWeldApp/resource/stop.png new file mode 100644 index 0000000000000000000000000000000000000000..c0f47b44d15ceeb73eefaca221f919572df1b6f6 GIT binary patch literal 1535 zcmV1O8~G>D@sk1ZyEw8~sB}P*Sk%_L^+Q@0{Bvm%ZJc-Pv4n%Uf7T z?)J^T`P`eCH*emI#^_Qapead{Fe8A9MnC|o8Q`(z=A_x!*fOhWtxVHe@N2F^eQx87 ztMUdb;`_hx?;rU70^@vC=lrMC)YNF#`SS%u4xz;>Zh?dC?YDEw zdJ-Bxiu+#N^`W)@U+@y{Q@Ar)BJpMS@bK@7-xV3a&sthmu1ux&L&+DR;9~W0s&yBAz6BXKQQwQQqBAC{=+n&gz=hvo)JNr}{2sfCGlnh0*nMIBTm^m8qya z=ND1=XS(zGqbl!J0yvmXzW|q^4+`9*qQnx)UW2o~t0$j76jq1O00+|P-LSg%mT`Y4 z%fw1>{>qko{sUQo`Ok#{Xj|TgF@SfSg#}C85ylb&11$D=j2)@sX6t5X*WfR14caFL z0$3bvYifq8ZorUjn;2MhU2hLEI!gl-eqLQFm8&9GMHbt_=gu~mrXlMmN&_4;jDv8J zw};iL%H%%Umdi=iRSKXzJ8ct=&6s+8w^-?n`IT`%1|0Ukj-Zzh@iZK*?2O9fwq!fFbIJG|nF zo&e$Vy#eLlQmH^9m11}9+{4yxzK@B&D{)z-$tF&oU}Jr~O#H2K!NGnL4z?=;SAfMZ zd`-;-yj&@gyngRKc5CM&Ds>1g`{y^u*}477z0YKFW@eq+@_;MAflZs9MU>^!(AwLA z9bX--nB^_hRaoB-Pduht1+gmlTz58GmW4Y&!#IIAZ&rQ#gG1k`7NI2n?ZXc!$=kv| z!Y?e#qAWmr>G9Vn{Jxj~w5Bz!`RQzSeBrNA79a+Xm+_zoRViOF0VqQEuXg8h4km5@ zkAqyNGCdR!7l5eB5Sp=ri36ZWx46>OBvL3;GX{$MV+8P0-RjkAx-yxPG!h5E!M3(G zuIu?ItS@E&*{rS`Pn|wp!o~rxKb_u=Vf!EfbP_oF?=gBry{vx)CUS484M(1Ny8K1&-ga71@brnga!Tr3oKz%t_lBz z57$Owe(@TGg)cY(U+@-B3q^ z4FL>hC;kAb340RtZ^HeCL4$}3L#aUoejvFqE&!$67;>aNo3OtljV4S}Id=>6pUP1% z!QBF-f(aDPatA0C&Z2;-D?qt`su0IOLLq%8N4b!`4UiU<761&is6@+}4)Y7PtZ7>z zEv(OR47RXd1W2hJb5Z9osU4JDbc0{G9l2FGYaGrc9d+ z9TcT4@VX>zcP(WY7@!cR4cBuJLjf$#QfSM6kz#0o^DL1Lose~9$~{NNo~Y2XW$ECZ z2q4nYPZ23lj1hsPBS0epS@a$05ZdTy(lN-vXl%{f3 lTk8lFCxD7D37{gt{{YM{=Wj@#Zi)Z^002ovPDHLkV1kqc*}VV& literal 0 HcmV?d00001 diff --git a/App/LapWeld/LapWeldApp/resources.qrc b/App/LapWeld/LapWeldApp/resources.qrc new file mode 100644 index 0000000..6889331 --- /dev/null +++ b/App/LapWeld/LapWeldApp/resources.qrc @@ -0,0 +1,26 @@ + + + resource/start.png + resource/stop.png + resource/camera_offline.png + resource/camera_online.png + resource/robot_offline.png + resource/robot_online.png + resource/dialog_cancel.png + resource/dialog_ok.png + resource/close.png + resource/hide.png + resource/logo.png + resource/logo.ico + resource/config_algo.png + resource/config_camera.png + resource/config_camera_level.png + resource/config_data_test.png + resource/config_algo_s.png + resource/config_camera_level_s.png + resource/config_camera_s.png + resource/config_data_test_s.png + resource/config_data_test.svg + resource/result_icon.png + + diff --git a/App/LapWeld/LapWeldApp/resultitem.cpp b/App/LapWeld/LapWeldApp/resultitem.cpp new file mode 100644 index 0000000..79809dc --- /dev/null +++ b/App/LapWeld/LapWeldApp/resultitem.cpp @@ -0,0 +1,44 @@ +#include "resultitem.h" +#include "ui_resultitem.h" + +ResultItem::ResultItem(QWidget *parent) + : QWidget(parent) + , ui(new Ui::ResultItem) +{ + ui->setupUi(this); + + // 强制应用样式表 - 这是关键! + this->setAttribute(Qt::WA_StyledBackground, true); + + // 初始化时设置样式 + setItemStyle(); +} + +ResultItem::~ResultItem() +{ + delete ui; +} + +void ResultItem::setResultData(int targetIndex, const LapWeldPosition& position) +{ + // 设置目标编号 + ui->result_id->setText(QString("目标:%1").arg(targetIndex)); + + // 设置数据到对应的Label控件 + ui->result_x->setText(QString("%1").arg(position.x, 0, 'f', 2)); + ui->result_y->setText(QString("%1").arg(position.y, 0, 'f', 2)); + ui->result_z->setText(QString("%1").arg(position.z, 0, 'f', 2)); + ui->result_rz->setText(QString("%1").arg(position.yaw, 0, 'f', 2)); + +} + +void ResultItem::setItemStyle() +{ + // 只设置右侧和下侧边框作为格子间分隔线,不修改背景和QLabel样式 + this->setStyleSheet( + "ResultItem { " + " border: 6px solid #191A1C; " // 右侧边框 + " border-radius: 0px; " // 去掉圆角,让分隔线更清晰 + "} " + ); +} diff --git a/App/LapWeld/LapWeldApp/resultitem.h b/App/LapWeld/LapWeldApp/resultitem.h new file mode 100644 index 0000000..c889eaa --- /dev/null +++ b/App/LapWeld/LapWeldApp/resultitem.h @@ -0,0 +1,29 @@ +#ifndef RESULTITEM_H +#define RESULTITEM_H + +#include +#include "IYLapWeldStatus.h" + +namespace Ui { +class ResultItem; +} + +class ResultItem : public QWidget +{ + Q_OBJECT + +public: + explicit ResultItem(QWidget *parent = nullptr); + ~ResultItem(); + + // 设置检测结果数据 + void setResultData(int targetIndex, const LapWeldPosition& position); + + // 设置样式 + void setItemStyle(); + +private: + Ui::ResultItem *ui; +}; + +#endif // RESULTITEM_H diff --git a/App/LapWeld/LapWeldApp/resultitem.ui b/App/LapWeld/LapWeldApp/resultitem.ui new file mode 100644 index 0000000..71521ff --- /dev/null +++ b/App/LapWeld/LapWeldApp/resultitem.ui @@ -0,0 +1,285 @@ + + + ResultItem + + + + 0 + 0 + 272 + 200 + + + + Form + + + background-color: rgb(37, 38, 42); + + + + + + 48 + 13 + 161 + 29 + + + + + 12 + + + + Qt::LayoutDirection::LeftToRight + + + color: rgb(239, 241, 245); +background-color: rgb(37, 38, 42); + + + 目标 + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + + + + + + 16 + 16 + 24 + 24 + + + + image: url(:/resource/result_icon.png); +background-color: rgba(255, 255, 255, 0); + + + + + + 59 + 43 + 58 + 30 + + + + + 12 + + + + Qt::LayoutDirection::RightToLeft + + + color: rgb(239, 241, 245); +background-color: rgb(37, 38, 42); + + + 坐标X: + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + 59 + 78 + 58 + 30 + + + + + 12 + + + + Qt::LayoutDirection::RightToLeft + + + color: rgb(239, 241, 245); +background-color: rgb(37, 38, 42); + + + 坐标Y: + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + 33 + 148 + 84 + 30 + + + + + 12 + + + + Qt::LayoutDirection::RightToLeft + + + color: rgb(239, 241, 245); +background-color: rgb(37, 38, 42); + + + 旋转角RZ: + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + 59 + 113 + 58 + 30 + + + + + 12 + + + + Qt::LayoutDirection::RightToLeft + + + color: rgb(239, 241, 245); +background-color: rgb(37, 38, 42); + + + 坐标Z: + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + 128 + 80 + 85 + 30 + + + + + 12 + + + + background-color: rgb(59, 61, 71); +color: rgb(239, 241, 245); +border: 1px solid #3B3D47; +padding: 5px; + + + + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + + + + + + 128 + 150 + 85 + 30 + + + + + 12 + + + + background-color: rgb(59, 61, 71); +color: rgb(239, 241, 245); +border: 1px solid #3B3D47; +padding: 5px; + + + + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + + + + + + 128 + 45 + 85 + 30 + + + + + 12 + + + + background-color: rgb(59, 61, 71); +color: rgb(239, 241, 245); +border: 1px solid #3B3D47; +padding: 5px; + + + + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + + + + + + 128 + 115 + 85 + 30 + + + + + 12 + + + + background-color: rgb(59, 61, 71); +color: rgb(239, 241, 245); +border: 1px solid #3B3D47; +padding: 5px; + + + + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + + + + + + diff --git a/App/LapWeld/LapWeldConfig/Inc/IVrConfig.h b/App/LapWeld/LapWeldConfig/Inc/IVrConfig.h new file mode 100644 index 0000000..73ee41e --- /dev/null +++ b/App/LapWeld/LapWeldConfig/Inc/IVrConfig.h @@ -0,0 +1,259 @@ +#ifndef IVRCONFIG_H +#define IVRCONFIG_H + +#include +#include +#include +#include + +/** + * @brief 项目类型枚举 + */ +enum class ProjectType +{ + LapWeld = 0, // 激光焊接 + DirectBag = 1, // 带方向的编织袋 +}; + +/** + * @brief 项目类型字符串转换函数 + */ +inline std::string ProjectTypeToString(ProjectType type) +{ + switch (type) { + case ProjectType::LapWeld: + return "LapWeld"; + case ProjectType::DirectBag: + return "DirectBag"; + default: + return "Unknown"; + } +} + +/** + * @brief 字符串转项目类型函数 + */ +inline ProjectType StringToProjectType(const std::string& str) +{ + if (str == "LapWeld" || str == "0") { + return ProjectType::LapWeld; + } else if (str == "DirectBag" || str == "1") { + return ProjectType::DirectBag; + } else { + return ProjectType::LapWeld; // 默认返回激光焊接 + } +} + +struct DeviceInfo +{ + std::string name; + std::string ip; +}; + +/** + * @brief 串口配置信息 + */ +struct SerialConfig +{ +#ifdef _WIN32 + std::string portName = "COM6"; // 串口名称 +#else + std::string portName = "/dev/ttyS3"; // 串口名称 +#endif + int baudRate = 115200; // 波特率 + int dataBits = 8; // 数据位 + int stopBits = 1; // 停止位 + int parity = 0; // 校验位 (0-无校验, 1-奇校验, 2-偶校验) + int flowControl = 0; // 流控制 (0-无, 1-硬件, 2-软件) + bool enabled = true; // 是否启用串口通信 +}; + + +/** + * @brief 离群点滤波参数 + */ +struct VrOutlierFilterParam +{ + double continuityTh = 20.0; // 连续性阈值 + int outlierTh = 5; // 离群点判断阈值 +}; + + +/** + * @brief 树生长参数 + */ +struct VrTreeGrowParam +{ + double yDeviation_max = 20.0; // 生长时允许的最大Y偏差 + double zDeviation_max = 80.0; // 生长时允许的最大Z偏差 + int maxLineSkipNum = 5; // 生长时允许跳过的最大线条数 + double maxSkipDistance = 20.0; // 最大跳跃距离 + double minLTypeTreeLen = 50.0; // L型树的最小长度 + double minVTypeTreeLen = 50.0; // V型树的最小长度 +}; + +/** + * @brief 激光焊接参数 + */ +struct VrLapWeldParam +{ + double lapHeight = 2.0;//搭接厚度 + double weldMinLen = 2.0; //最小焊缝长度,用于过滤可能的虚假焊缝 + int weldRefPoints = 2; //输出的直线焊缝的参考点,默认是2个(起点和终点) +}; + + +/** + * @brief 单个相机的平面校准参数 + */ +struct VrCameraPlaneCalibParam +{ + int cameraIndex = 1; // 相机索引(1-based) + std::string cameraName = ""; // 相机名称 + double planeCalib[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; // 旋转矩阵,将数据调平(默认单位矩阵) + double planeHeight = -1.0; // 参考平面的高度,用于去除地面数据 + double invRMatrix[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; // 逆旋转矩阵,回到原坐标系(默认单位矩阵) + bool isCalibrated = false; // 是否已经校准 +}; + +/** + * @brief 平面校准参数(支持多相机) + */ +struct VrPlaneCalibParam +{ + std::vector cameraCalibParams; // 各个相机的校准参数 + + // 获取指定相机的校准参数 + VrCameraPlaneCalibParam* GetCameraCalibParam(int cameraIndex) { + for (auto& param : cameraCalibParams) { + if (param.cameraIndex == cameraIndex) { + return ¶m; + } + } + return nullptr; + } + + // 获取指定相机的校准参数(const版本) + const VrCameraPlaneCalibParam* GetCameraCalibParam(int cameraIndex) const { + for (const auto& param : cameraCalibParams) { + if (param.cameraIndex == cameraIndex) { + return ¶m; + } + } + return nullptr; + } + + // 设置或更新指定相机的校准参数 + void SetCameraCalibParam(const VrCameraPlaneCalibParam& param) { + for (auto& existingParam : cameraCalibParams) { + if (existingParam.cameraIndex == param.cameraIndex) { + existingParam = param; + return; + } + } + // 如果不存在,则添加新的 + cameraCalibParams.push_back(param); + } + + // 移除指定相机的校准参数 + void RemoveCameraCalibParam(int cameraIndex) { + cameraCalibParams.erase( + std::remove_if(cameraCalibParams.begin(), cameraCalibParams.end(), + [cameraIndex](const VrCameraPlaneCalibParam& param) { + return param.cameraIndex == cameraIndex; + }), + cameraCalibParams.end()); + } +}; + +/** + * @brief 调试参数 + */ +struct VrDebugParam +{ + bool enableDebug = true; // 是否开启调试模式 + bool savePointCloud = false; // 是否保存点云数据 + bool saveDebugImage = false; // 是否保存调试图像 + bool printDetailLog = true; // 是否打印详细日志 + std::string debugOutputPath = ""; // 调试输出路径 +}; + +/** + * @brief 算法参数配置结构 + */ +struct VrAlgorithmParams +{ + VrOutlierFilterParam filterParam; // 滤波参数 + VrTreeGrowParam growParam; // 增长参数 + VrPlaneCalibParam planeCalibParam; // 平面校准参数 + VrLapWeldParam lapWeldParam; // 激光焊接参数 +}; + +/** + * @brief 配置加载结果 + */ +struct ConfigResult +{ + std::vector cameraList; + std::vector deviceList; + VrAlgorithmParams algorithmParams; // 算法参数 + VrDebugParam debugParam; // 调试参数 + SerialConfig serialConfig; // 串口配置 + ProjectType projectType; // 项目类型 +}; + +/** + * @brief 配置改变通知接口 + */ +class IVrConfigChangeNotify +{ +public: + virtual ~IVrConfigChangeNotify() {} + + /** + * @brief 配置数据改变通知 + * @param configResult 新的配置数据 + */ + virtual void OnConfigChanged(const ConfigResult& configResult) = 0; +}; + +/** + * @brief VrConfig接口类 + */ +class IVrConfig +{ +public: + /** + * @brief 虚析构函数 + */ + virtual ~IVrConfig() {} + + /** + * @brief 创建实例 + * @return 实例 + */ + static bool CreateInstance(IVrConfig** ppVrConfig); + + /** + * @brief 加载配置文件 + * @param filePath 配置文件路径 + * @return 加载的配置结果 + */ + virtual ConfigResult LoadConfig(const std::string& filePath) = 0; + + /** + * @brief 保存配置文件 + * @param filePath 配置文件路径 + * @param configResult 配置结果 + * @return 是否保存成功 + */ + virtual bool SaveConfig(const std::string& filePath, ConfigResult& configResult) = 0; + + /** + * @brief 设置配置改变通知回调 + * @param notify 通知接口指针 + */ + virtual void SetConfigChangeNotify(IVrConfigChangeNotify* notify) = 0; +}; + +#endif // IVRCONFIG_H diff --git a/App/LapWeld/LapWeldConfig/LapWeldConfig.pro b/App/LapWeld/LapWeldConfig/LapWeldConfig.pro new file mode 100644 index 0000000..a197061 --- /dev/null +++ b/App/LapWeld/LapWeldConfig/LapWeldConfig.pro @@ -0,0 +1,39 @@ +#CONFIG -= qt + +TEMPLATE = lib +CONFIG += staticlib +CONFIG += c++11 + + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +INCLUDEPATH += ./Inc +INCLUDEPATH += ./_Inc + +SOURCES += \ + Src/VrConfig.cpp + +HEADERS += \ + Inc/IVrConfig.h \ + _Inc/VrConfig.h + +#utils +INCLUDEPATH += $$PWD/../../../VrUtils/Inc +INCLUDEPATH += $$PWD/../../../VrUtils/tinyxml2 + +win32:CONFIG(debug, debug|release) { + LIBS += -L../../../VrUtils/Debug -lVrUtils +}else:win32:CONFIG(release, debug|release){ + LIBS += -L../../../VrUtils/release -lVrUtils +}else:unix:!macx { + # Unix/Linux平台库链接(including cross-compilation) + LIBS += -L../../../VrUtils -lVrUtils +} + +# Default rules for deployment. +unix { + target.path = /usr/lib +} +!isEmpty(target.path): INSTALLS += target diff --git a/App/LapWeld/LapWeldConfig/Src/VrConfig.cpp b/App/LapWeld/LapWeldConfig/Src/VrConfig.cpp new file mode 100644 index 0000000..fdd35bc --- /dev/null +++ b/App/LapWeld/LapWeldConfig/Src/VrConfig.cpp @@ -0,0 +1,455 @@ +#include "VrConfig.h" +#include +#include +#include + +using namespace tinyxml2; + +CVrConfig::CVrConfig() : m_pNotify(nullptr) +{ + // 构造函数 +} + +CVrConfig::~CVrConfig() +{ + // 析构函数 +} + +ConfigResult CVrConfig::LoadConfig(const std::string& filePath) +{ + ConfigResult result; + + // 使用tinyxml2库加载XML文件 + XMLDocument doc; + XMLError err = doc.LoadFile(filePath.c_str()); + if (err != XML_SUCCESS) + { + std::cerr << "open config file failed: " << filePath << std::endl; + return result; + } + + // 获取根元素 + XMLElement* root = doc.RootElement(); + if (!root || std::string(root->Name()) != "LapWeldConfig") + { + std::cerr << "config file format error: root element is not LapWeldConfig" << std::endl; + return result; + } + + // 解析摄像头列表 + XMLElement* camerasElement = root->FirstChildElement("Cameras"); + if (camerasElement) + { + XMLElement* cameraElement = camerasElement->FirstChildElement("Camera"); + while (cameraElement) + { + DeviceInfo camera; + if (cameraElement->Attribute("name")) + camera.name = cameraElement->Attribute("name"); + + if (cameraElement->Attribute("ip")) + camera.ip = cameraElement->Attribute("ip"); + + result.cameraList.push_back(camera); + cameraElement = cameraElement->NextSiblingElement("Camera"); + } + } + + // 解析设备列表 + XMLElement* devicesElement = root->FirstChildElement("Devices"); + if (devicesElement) + { + XMLElement* deviceElement = devicesElement->FirstChildElement("Device"); + while (deviceElement) + { + DeviceInfo device; + if (deviceElement->Attribute("name")) + device.name = deviceElement->Attribute("name"); + + if (deviceElement->Attribute("ip")) + device.ip = deviceElement->Attribute("ip"); + + result.deviceList.push_back(device); + deviceElement = deviceElement->NextSiblingElement("Device"); + } + } + + // 解析算法参数 + XMLElement* algoParamsElement = root->FirstChildElement("AlgorithmParams"); + if (algoParamsElement) + { + // 解析激光焊接参数 + XMLElement* lapWeldParamElement = algoParamsElement->FirstChildElement("LapWeldParam"); + if (lapWeldParamElement) + { + if (lapWeldParamElement->Attribute("lapHeight")) + result.algorithmParams.lapWeldParam.lapHeight = lapWeldParamElement->DoubleAttribute("lapHeight"); + if (lapWeldParamElement->Attribute("weldMinLen")) + result.algorithmParams.lapWeldParam.weldMinLen = lapWeldParamElement->DoubleAttribute("weldMinLen"); + if (lapWeldParamElement->Attribute("weldRefPoints")) + result.algorithmParams.lapWeldParam.weldRefPoints = lapWeldParamElement->IntAttribute("weldRefPoints"); + } + + // 解析滤波参数 + XMLElement* filterParamElement = algoParamsElement->FirstChildElement("FilterParam"); + if (filterParamElement) + { + if (filterParamElement->Attribute("continuityTh")) + result.algorithmParams.filterParam.continuityTh = filterParamElement->DoubleAttribute("continuityTh"); + if (filterParamElement->Attribute("outlierTh")) + result.algorithmParams.filterParam.outlierTh = filterParamElement->IntAttribute("outlierTh"); + } + + // 解析增长参数 + XMLElement* growParamElement = algoParamsElement->FirstChildElement("GrowParam"); + if (growParamElement) + { + if (growParamElement->Attribute("maxLineSkipNum")) + result.algorithmParams.growParam.maxLineSkipNum = growParamElement->IntAttribute("maxLineSkipNum"); + if (growParamElement->Attribute("yDeviation_max")) + result.algorithmParams.growParam.yDeviation_max = growParamElement->DoubleAttribute("yDeviation_max"); + if (growParamElement->Attribute("maxSkipDistance")) + result.algorithmParams.growParam.maxSkipDistance = growParamElement->DoubleAttribute("maxSkipDistance"); + if (growParamElement->Attribute("zDeviation_max")) + result.algorithmParams.growParam.zDeviation_max = growParamElement->DoubleAttribute("zDeviation_max"); + if (growParamElement->Attribute("minLTypeTreeLen")) + result.algorithmParams.growParam.minLTypeTreeLen = growParamElement->DoubleAttribute("minLTypeTreeLen"); + if (growParamElement->Attribute("minVTypeTreeLen")) + result.algorithmParams.growParam.minVTypeTreeLen = growParamElement->DoubleAttribute("minVTypeTreeLen"); + } + + // 解析多相机平面校准参数 + XMLElement* planeCalibParamsElement = algoParamsElement->FirstChildElement("PlaneCalibParams"); + if (planeCalibParamsElement) + { + XMLElement* cameraCalibParamElement = planeCalibParamsElement->FirstChildElement("CameraCalibParam"); + while (cameraCalibParamElement) + { + VrCameraPlaneCalibParam cameraParam; + + // 读取相机基础信息 + if (cameraCalibParamElement->Attribute("cameraIndex")) + cameraParam.cameraIndex = cameraCalibParamElement->IntAttribute("cameraIndex"); + if (cameraCalibParamElement->Attribute("cameraName")) + cameraParam.cameraName = cameraCalibParamElement->Attribute("cameraName"); + if (cameraCalibParamElement->Attribute("isCalibrated")) + cameraParam.isCalibrated = cameraCalibParamElement->BoolAttribute("isCalibrated"); + if (cameraCalibParamElement->Attribute("planeHeight")) + cameraParam.planeHeight = cameraCalibParamElement->DoubleAttribute("planeHeight"); + + // 读取旋转矩阵planeCalib[9] (3x3矩阵) + if (cameraCalibParamElement->Attribute("planeCalib_00")) + cameraParam.planeCalib[0] = cameraCalibParamElement->DoubleAttribute("planeCalib_00"); + if (cameraCalibParamElement->Attribute("planeCalib_01")) + cameraParam.planeCalib[1] = cameraCalibParamElement->DoubleAttribute("planeCalib_01"); + if (cameraCalibParamElement->Attribute("planeCalib_02")) + cameraParam.planeCalib[2] = cameraCalibParamElement->DoubleAttribute("planeCalib_02"); + if (cameraCalibParamElement->Attribute("planeCalib_10")) + cameraParam.planeCalib[3] = cameraCalibParamElement->DoubleAttribute("planeCalib_10"); + if (cameraCalibParamElement->Attribute("planeCalib_11")) + cameraParam.planeCalib[4] = cameraCalibParamElement->DoubleAttribute("planeCalib_11"); + if (cameraCalibParamElement->Attribute("planeCalib_12")) + cameraParam.planeCalib[5] = cameraCalibParamElement->DoubleAttribute("planeCalib_12"); + if (cameraCalibParamElement->Attribute("planeCalib_20")) + cameraParam.planeCalib[6] = cameraCalibParamElement->DoubleAttribute("planeCalib_20"); + if (cameraCalibParamElement->Attribute("planeCalib_21")) + cameraParam.planeCalib[7] = cameraCalibParamElement->DoubleAttribute("planeCalib_21"); + if (cameraCalibParamElement->Attribute("planeCalib_22")) + cameraParam.planeCalib[8] = cameraCalibParamElement->DoubleAttribute("planeCalib_22"); + + // 读取逆旋转矩阵invRMatrix[9] (3x3矩阵) + if (cameraCalibParamElement->Attribute("invRMatrix_00")) + cameraParam.invRMatrix[0] = cameraCalibParamElement->DoubleAttribute("invRMatrix_00"); + if (cameraCalibParamElement->Attribute("invRMatrix_01")) + cameraParam.invRMatrix[1] = cameraCalibParamElement->DoubleAttribute("invRMatrix_01"); + if (cameraCalibParamElement->Attribute("invRMatrix_02")) + cameraParam.invRMatrix[2] = cameraCalibParamElement->DoubleAttribute("invRMatrix_02"); + if (cameraCalibParamElement->Attribute("invRMatrix_10")) + cameraParam.invRMatrix[3] = cameraCalibParamElement->DoubleAttribute("invRMatrix_10"); + if (cameraCalibParamElement->Attribute("invRMatrix_11")) + cameraParam.invRMatrix[4] = cameraCalibParamElement->DoubleAttribute("invRMatrix_11"); + if (cameraCalibParamElement->Attribute("invRMatrix_12")) + cameraParam.invRMatrix[5] = cameraCalibParamElement->DoubleAttribute("invRMatrix_12"); + if (cameraCalibParamElement->Attribute("invRMatrix_20")) + cameraParam.invRMatrix[6] = cameraCalibParamElement->DoubleAttribute("invRMatrix_20"); + if (cameraCalibParamElement->Attribute("invRMatrix_21")) + cameraParam.invRMatrix[7] = cameraCalibParamElement->DoubleAttribute("invRMatrix_21"); + if (cameraCalibParamElement->Attribute("invRMatrix_22")) + cameraParam.invRMatrix[8] = cameraCalibParamElement->DoubleAttribute("invRMatrix_22"); + + // 添加到结果中 + result.algorithmParams.planeCalibParam.cameraCalibParams.push_back(cameraParam); + + // 移动到下一个相机配置 + cameraCalibParamElement = cameraCalibParamElement->NextSiblingElement("CameraCalibParam"); + } + } + + // 兼容性处理:如果存在旧格式的PlaneCalibParam,转换为新格式 + XMLElement* oldPlaneCalibParamElement = algoParamsElement->FirstChildElement("PlaneCalibParam"); + if (oldPlaneCalibParamElement && result.algorithmParams.planeCalibParam.cameraCalibParams.empty()) + { + VrCameraPlaneCalibParam cameraParam; + cameraParam.cameraIndex = 1; // 默认为第一个相机 + cameraParam.cameraName = "默认相机"; + cameraParam.isCalibrated = false; + + if (oldPlaneCalibParamElement->Attribute("planeHeight")) + cameraParam.planeHeight = oldPlaneCalibParamElement->DoubleAttribute("planeHeight"); + + // 读取旋转矩阵 + if (oldPlaneCalibParamElement->Attribute("planeCalib_00")) + cameraParam.planeCalib[0] = oldPlaneCalibParamElement->DoubleAttribute("planeCalib_00"); + if (oldPlaneCalibParamElement->Attribute("planeCalib_01")) + cameraParam.planeCalib[1] = oldPlaneCalibParamElement->DoubleAttribute("planeCalib_01"); + if (oldPlaneCalibParamElement->Attribute("planeCalib_02")) + cameraParam.planeCalib[2] = oldPlaneCalibParamElement->DoubleAttribute("planeCalib_02"); + if (oldPlaneCalibParamElement->Attribute("planeCalib_10")) + cameraParam.planeCalib[3] = oldPlaneCalibParamElement->DoubleAttribute("planeCalib_10"); + if (oldPlaneCalibParamElement->Attribute("planeCalib_11")) + cameraParam.planeCalib[4] = oldPlaneCalibParamElement->DoubleAttribute("planeCalib_11"); + if (oldPlaneCalibParamElement->Attribute("planeCalib_12")) + cameraParam.planeCalib[5] = oldPlaneCalibParamElement->DoubleAttribute("planeCalib_12"); + if (oldPlaneCalibParamElement->Attribute("planeCalib_20")) + cameraParam.planeCalib[6] = oldPlaneCalibParamElement->DoubleAttribute("planeCalib_20"); + if (oldPlaneCalibParamElement->Attribute("planeCalib_21")) + cameraParam.planeCalib[7] = oldPlaneCalibParamElement->DoubleAttribute("planeCalib_21"); + if (oldPlaneCalibParamElement->Attribute("planeCalib_22")) + cameraParam.planeCalib[8] = oldPlaneCalibParamElement->DoubleAttribute("planeCalib_22"); + + // 读取逆旋转矩阵 + if (oldPlaneCalibParamElement->Attribute("invRMatrix_00")) + cameraParam.invRMatrix[0] = oldPlaneCalibParamElement->DoubleAttribute("invRMatrix_00"); + if (oldPlaneCalibParamElement->Attribute("invRMatrix_01")) + cameraParam.invRMatrix[1] = oldPlaneCalibParamElement->DoubleAttribute("invRMatrix_01"); + if (oldPlaneCalibParamElement->Attribute("invRMatrix_02")) + cameraParam.invRMatrix[2] = oldPlaneCalibParamElement->DoubleAttribute("invRMatrix_02"); + if (oldPlaneCalibParamElement->Attribute("invRMatrix_10")) + cameraParam.invRMatrix[3] = oldPlaneCalibParamElement->DoubleAttribute("invRMatrix_10"); + if (oldPlaneCalibParamElement->Attribute("invRMatrix_11")) + cameraParam.invRMatrix[4] = oldPlaneCalibParamElement->DoubleAttribute("invRMatrix_11"); + if (oldPlaneCalibParamElement->Attribute("invRMatrix_12")) + cameraParam.invRMatrix[5] = oldPlaneCalibParamElement->DoubleAttribute("invRMatrix_12"); + if (oldPlaneCalibParamElement->Attribute("invRMatrix_20")) + cameraParam.invRMatrix[6] = oldPlaneCalibParamElement->DoubleAttribute("invRMatrix_20"); + if (oldPlaneCalibParamElement->Attribute("invRMatrix_21")) + cameraParam.invRMatrix[7] = oldPlaneCalibParamElement->DoubleAttribute("invRMatrix_21"); + if (oldPlaneCalibParamElement->Attribute("invRMatrix_22")) + cameraParam.invRMatrix[8] = oldPlaneCalibParamElement->DoubleAttribute("invRMatrix_22"); + + result.algorithmParams.planeCalibParam.cameraCalibParams.push_back(cameraParam); + } + + } + + // 解析调试参数(在AlgorithmParams外面) + XMLElement* debugParamElement = root->FirstChildElement("DebugParam"); + if (debugParamElement) + { + if (debugParamElement->Attribute("enableDebug")) + result.debugParam.enableDebug = debugParamElement->BoolAttribute("enableDebug"); + if (debugParamElement->Attribute("savePointCloud")) + result.debugParam.savePointCloud = debugParamElement->BoolAttribute("savePointCloud"); + if (debugParamElement->Attribute("saveDebugImage")) + result.debugParam.saveDebugImage = debugParamElement->BoolAttribute("saveDebugImage"); + if (debugParamElement->Attribute("printDetailLog")) + result.debugParam.printDetailLog = debugParamElement->BoolAttribute("printDetailLog"); + if (debugParamElement->Attribute("debugOutputPath")) + result.debugParam.debugOutputPath = debugParamElement->Attribute("debugOutputPath"); + } + + // 解析项目类型(默认为拆包) + result.projectType = ProjectType::LapWeld; // 设置默认值 + XMLElement* projectTypeElement = root->FirstChildElement("ProjectType"); + if (projectTypeElement) + { + if (projectTypeElement->Attribute("type")) + { + std::string typeStr = projectTypeElement->Attribute("type"); + result.projectType = StringToProjectType(typeStr); + } + } + + // 解析串口配置 + XMLElement* serialConfigElement = root->FirstChildElement("SerialConfig"); + if (serialConfigElement) + { + if (serialConfigElement->Attribute("portName")) + result.serialConfig.portName = serialConfigElement->Attribute("portName"); + if (serialConfigElement->Attribute("baudRate")) + result.serialConfig.baudRate = serialConfigElement->IntAttribute("baudRate"); + if (serialConfigElement->Attribute("dataBits")) + result.serialConfig.dataBits = serialConfigElement->IntAttribute("dataBits"); + if (serialConfigElement->Attribute("stopBits")) + result.serialConfig.stopBits = serialConfigElement->IntAttribute("stopBits"); + if (serialConfigElement->Attribute("parity")) + result.serialConfig.parity = serialConfigElement->IntAttribute("parity"); + if (serialConfigElement->Attribute("flowControl")) + result.serialConfig.flowControl = serialConfigElement->IntAttribute("flowControl"); + if (serialConfigElement->Attribute("enabled")) + result.serialConfig.enabled = serialConfigElement->BoolAttribute("enabled"); + } + + return result; +} + +bool CVrConfig::SaveConfig(const std::string& filePath, ConfigResult& configResult) +{ + // 创建XML文档 + XMLDocument doc; + + // 添加声明 + XMLDeclaration* declaration = doc.NewDeclaration("xml version=\"1.0\" encoding=\"UTF-8\""); + doc.InsertFirstChild(declaration); + + // 创建根元素 + XMLElement* root = doc.NewElement("LapWeldConfig"); + doc.InsertEndChild(root); + + // 添加摄像头列表 + XMLElement* camerasElement = doc.NewElement("Cameras"); + root->InsertEndChild(camerasElement); + + for (const auto& camera : configResult.cameraList) + { + XMLElement* cameraElement = doc.NewElement("Camera"); + cameraElement->SetAttribute("name", camera.name.c_str()); + cameraElement->SetAttribute("ip", camera.ip.c_str()); + camerasElement->InsertEndChild(cameraElement); + } + + // 添加设备列表 + XMLElement* devicesElement = doc.NewElement("Devices"); + root->InsertEndChild(devicesElement); + + for (const auto& device : configResult.deviceList) + { + XMLElement* deviceElement = doc.NewElement("Device"); + deviceElement->SetAttribute("name", device.name.c_str()); + deviceElement->SetAttribute("ip", device.ip.c_str()); + devicesElement->InsertEndChild(deviceElement); + } + + // 添加算法参数 + XMLElement* algoParamsElement = doc.NewElement("AlgorithmParams"); + root->InsertEndChild(algoParamsElement); + + // 添加激光焊接参数 + XMLElement* lapWeldParamElement = doc.NewElement("LapWeldParam"); + lapWeldParamElement->SetAttribute("lapHeight", configResult.algorithmParams.lapWeldParam.lapHeight); + lapWeldParamElement->SetAttribute("weldMinLen", configResult.algorithmParams.lapWeldParam.weldMinLen); + lapWeldParamElement->SetAttribute("weldRefPoints", configResult.algorithmParams.lapWeldParam.weldRefPoints); + algoParamsElement->InsertEndChild(lapWeldParamElement); + + // 添加滤波参数 + XMLElement* filterParamElement = doc.NewElement("FilterParam"); + filterParamElement->SetAttribute("continuityTh", configResult.algorithmParams.filterParam.continuityTh); + filterParamElement->SetAttribute("outlierTh", configResult.algorithmParams.filterParam.outlierTh); + algoParamsElement->InsertEndChild(filterParamElement); + + // 添加增长参数 + XMLElement* growParamElement = doc.NewElement("GrowParam"); + growParamElement->SetAttribute("maxLineSkipNum", configResult.algorithmParams.growParam.maxLineSkipNum); + growParamElement->SetAttribute("yDeviation_max", configResult.algorithmParams.growParam.yDeviation_max); + growParamElement->SetAttribute("maxSkipDistance", configResult.algorithmParams.growParam.maxSkipDistance); + growParamElement->SetAttribute("zDeviation_max", configResult.algorithmParams.growParam.zDeviation_max); + growParamElement->SetAttribute("minLTypeTreeLen", configResult.algorithmParams.growParam.minLTypeTreeLen); + growParamElement->SetAttribute("minVTypeTreeLen", configResult.algorithmParams.growParam.minVTypeTreeLen); + algoParamsElement->InsertEndChild(growParamElement); + + // 添加多相机平面校准参数 + XMLElement* planeCalibParamsElement = doc.NewElement("PlaneCalibParams"); + + for (const auto& cameraParam : configResult.algorithmParams.planeCalibParam.cameraCalibParams) + { + XMLElement* cameraCalibParamElement = doc.NewElement("CameraCalibParam"); + cameraCalibParamElement->SetAttribute("cameraIndex", cameraParam.cameraIndex); + cameraCalibParamElement->SetAttribute("cameraName", cameraParam.cameraName.c_str()); + cameraCalibParamElement->SetAttribute("isCalibrated", cameraParam.isCalibrated); + cameraCalibParamElement->SetAttribute("planeHeight", cameraParam.planeHeight); + + // 保存旋转矩阵planeCalib[9] (3x3矩阵) + cameraCalibParamElement->SetAttribute("planeCalib_00", cameraParam.planeCalib[0]); + cameraCalibParamElement->SetAttribute("planeCalib_01", cameraParam.planeCalib[1]); + cameraCalibParamElement->SetAttribute("planeCalib_02", cameraParam.planeCalib[2]); + cameraCalibParamElement->SetAttribute("planeCalib_10", cameraParam.planeCalib[3]); + cameraCalibParamElement->SetAttribute("planeCalib_11", cameraParam.planeCalib[4]); + cameraCalibParamElement->SetAttribute("planeCalib_12", cameraParam.planeCalib[5]); + cameraCalibParamElement->SetAttribute("planeCalib_20", cameraParam.planeCalib[6]); + cameraCalibParamElement->SetAttribute("planeCalib_21", cameraParam.planeCalib[7]); + cameraCalibParamElement->SetAttribute("planeCalib_22", cameraParam.planeCalib[8]); + + // 保存逆旋转矩阵invRMatrix[9] (3x3矩阵) + cameraCalibParamElement->SetAttribute("invRMatrix_00", cameraParam.invRMatrix[0]); + cameraCalibParamElement->SetAttribute("invRMatrix_01", cameraParam.invRMatrix[1]); + cameraCalibParamElement->SetAttribute("invRMatrix_02", cameraParam.invRMatrix[2]); + cameraCalibParamElement->SetAttribute("invRMatrix_10", cameraParam.invRMatrix[3]); + cameraCalibParamElement->SetAttribute("invRMatrix_11", cameraParam.invRMatrix[4]); + cameraCalibParamElement->SetAttribute("invRMatrix_12", cameraParam.invRMatrix[5]); + cameraCalibParamElement->SetAttribute("invRMatrix_20", cameraParam.invRMatrix[6]); + cameraCalibParamElement->SetAttribute("invRMatrix_21", cameraParam.invRMatrix[7]); + cameraCalibParamElement->SetAttribute("invRMatrix_22", cameraParam.invRMatrix[8]); + + planeCalibParamsElement->InsertEndChild(cameraCalibParamElement); + } + + algoParamsElement->InsertEndChild(planeCalibParamsElement); + + // 添加项目类型 + XMLElement* projectTypeElement = doc.NewElement("ProjectType"); + projectTypeElement->SetAttribute("type", ProjectTypeToString(configResult.projectType).c_str()); + root->InsertEndChild(projectTypeElement); + + // 添加调试参数(在AlgorithmParams外面) + XMLElement* debugParamElement = doc.NewElement("DebugParam"); + debugParamElement->SetAttribute("enableDebug", configResult.debugParam.enableDebug); + debugParamElement->SetAttribute("savePointCloud", configResult.debugParam.savePointCloud); + debugParamElement->SetAttribute("saveDebugImage", configResult.debugParam.saveDebugImage); + debugParamElement->SetAttribute("printDetailLog", configResult.debugParam.printDetailLog); + debugParamElement->SetAttribute("debugOutputPath", configResult.debugParam.debugOutputPath.c_str()); + root->InsertEndChild(debugParamElement); + + // 添加串口配置 + XMLElement* serialConfigElement = doc.NewElement("SerialConfig"); + serialConfigElement->SetAttribute("portName", configResult.serialConfig.portName.c_str()); + serialConfigElement->SetAttribute("baudRate", configResult.serialConfig.baudRate); + serialConfigElement->SetAttribute("dataBits", configResult.serialConfig.dataBits); + serialConfigElement->SetAttribute("stopBits", configResult.serialConfig.stopBits); + serialConfigElement->SetAttribute("parity", configResult.serialConfig.parity); + serialConfigElement->SetAttribute("flowControl", configResult.serialConfig.flowControl); + serialConfigElement->SetAttribute("enabled", configResult.serialConfig.enabled); + root->InsertEndChild(serialConfigElement); + + // 保存到文件 + XMLError err = doc.SaveFile(filePath.c_str()); + if (err != XML_SUCCESS) + { + std::cerr << "无法保存配置文件: " << filePath << std::endl; + return false; + } + + // 触发配置改变通知 + if (m_pNotify) + { + m_pNotify->OnConfigChanged(configResult); + } + + return true; +} + +// 设置配置改变通知回调 +void CVrConfig::SetConfigChangeNotify(IVrConfigChangeNotify* notify) +{ + m_pNotify = notify; +} + + +/** + * @brief 创建实例 + * @return 实例 + */ +bool IVrConfig::CreateInstance(IVrConfig** ppVrConfig) +{ + *ppVrConfig = new CVrConfig() ; + return *ppVrConfig != nullptr; +} diff --git a/App/LapWeld/LapWeldConfig/_Inc/VrConfig.h b/App/LapWeld/LapWeldConfig/_Inc/VrConfig.h new file mode 100644 index 0000000..4c4d5ae --- /dev/null +++ b/App/LapWeld/LapWeldConfig/_Inc/VrConfig.h @@ -0,0 +1,49 @@ +#ifndef VRCONFIG_H +#define VRCONFIG_H + +#include "IVrConfig.h" +#include "tinyxml2.h" +#include + +/** + * @brief 实现IVrConfig接口的配置类 + */ +class CVrConfig : public IVrConfig +{ +public: + /** + * @brief 构造函数 + */ + CVrConfig(); + + /** + * @brief 析构函数 + */ + virtual ~CVrConfig(); + + /** + * @brief 加载配置文件 + * @param filePath 配置文件路径 + * @return 加载的配置结果 + */ + virtual ConfigResult LoadConfig(const std::string& filePath) override; + + /** + * @brief 保存配置文件 + * @param filePath 配置文件路径 + * @param configResult 配置结果 + * @return 是否保存成功 + */ + virtual bool SaveConfig(const std::string& filePath, ConfigResult& configResult) override; + + /** + * @brief 设置配置改变通知回调 + * @param notify 通知接口指针 + */ + virtual void SetConfigChangeNotify(IVrConfigChangeNotify* notify) override; + +private: + IVrConfigChangeNotify* m_pNotify = nullptr; // 配置改变通知回调 +}; + +#endif // VRCONFIG_H diff --git a/App/LapWeld/LapWeldConfig/config/EyeHandCalibMatrixInfo.ini b/App/LapWeld/LapWeldConfig/config/EyeHandCalibMatrixInfo.ini new file mode 100644 index 0000000..78149a0 --- /dev/null +++ b/App/LapWeld/LapWeldConfig/config/EyeHandCalibMatrixInfo.ini @@ -0,0 +1,26 @@ +[CommInfo] +nMaxMatrixNum=8 +nExistMatrixNum=1 + +[CalibMatrixInfo_0] +nCalibPosIdx=0 +eCalibType=0 +eCalibMode=0 +sCalibPosName=0 +sCalibTime=2025-06-26-12-11-47 +dCalibMatrix_0=-0.6465967149138568 +dCalibMatrix_1=0.761458743338502 +dCalibMatrix_2=0.04575227268627502 +dCalibMatrix_3=759.0215603044383 +dCalibMatrix_4=0.7621532947207551 +dCalibMatrix_5=0.6473879189067671 +dCalibMatrix_6=-0.0033522827841527825 +dCalibMatrix_7=-403.08726353855 +dCalibMatrix_8=-0.03217209363575841 +dCalibMatrix_9=0.03270267033311197 +dCalibMatrix_10=-0.9989471916693979 +dCalibMatrix_11=801.5996530934686 +dCalibMatrix_12=0 +dCalibMatrix_13=0 +dCalibMatrix_14=0 +dCalibMatrix_15=1 diff --git a/App/LapWeld/LapWeldConfig/config/config.xml b/App/LapWeld/LapWeldConfig/config/config.xml new file mode 100644 index 0000000..26a9f94 --- /dev/null +++ b/App/LapWeld/LapWeldConfig/config/config.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 + 0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/GrabBagPrj/GrabBagPrj.pro b/GrabBagPrj/GrabBagPrj.pro index 7c427cd..f6efc00 100644 --- a/GrabBagPrj/GrabBagPrj.pro +++ b/GrabBagPrj/GrabBagPrj.pro @@ -10,15 +10,17 @@ SUBDIRS += ../Module/ModbusTCPServer/ModbusTCPServer.pro SUBDIRS += ../VrEyeDevice/VrEyeDevice.pro +SUBDIRS += ../VrNets/VrQTcpClient.pro +SUBDIRS += ../VrNets/VrTcpServer.pro + # 拆包项目 -SUBDIRS += ../GrabBagConfig/GrabBagConfig.pro -SUBDIRS += ../GrabBagApp/GrabBagApp.pro -# SUBDIRS += ../GrabBagConfigCmd +SUBDIRS += ../App/GrabBag/GrabBag.pro # 撕裂项目 -SUBDIRS += ../VrNets/VrTcpClient.pro -SUBDIRS += ../BeltTearingConfig/BeltTearingConfig.pro -SUBDIRS += ../BeltTearingApp/BeltTearingApp.pro +# SUBDIRS += ../App/BeltTearing/BeltTearing.pro + +#焊接 +# SUBDIRS += ../App/LapWeld/LapWeld.pro + + -SUBDIRS += ../VrNets/VrTcpServer.pro -SUBDIRS += ../BeltTearingServer/BeltTearingServer.pro diff --git a/GrabBagPrj/pkg_grabbagapp.sh b/GrabBagPrj/pkg_grabbagapp.sh index cfef919..50142c2 100644 --- a/GrabBagPrj/pkg_grabbagapp.sh +++ b/GrabBagPrj/pkg_grabbagapp.sh @@ -5,7 +5,7 @@ PKG_NAME="GrabBag" PKG_ARCH="arm64" # 从Version.h文件中读取版本信息 -VERSION_FILE="../GrabBagApp/Version.h" +VERSION_FILE="../App/GrabBag/GrabBagApp/Version.h" if [ -f "${VERSION_FILE}" ]; then # 读取版本号(从 GRABBAG_VERSION_STRING 中提取) @@ -51,20 +51,35 @@ fi #QT depend QT_PKG_PATH=/opt/firefly_qt5.15_arm64_20.04 +QT_LIB_PATH=/opt/sysroot/firefly-arm64-sysroot-20.04/lib/aarch64-linux-gnu echo "创建打包目录结构..." mkdir -p ${PKG_PATH}/DEBIAN mkdir -p ${PKG_PATH}/etc/profile.d mkdir -p ${PKG_PATH}/etc/xdg/autostart -mkdir -p ${PKG_PATH}/opt +mkdir -p ${PKG_PATH}/opt/sysroot/lib mkdir -p ${PKG_PATH}/usr/local/bin mkdir -p ${PKG_PATH}/usr/lib mkdir -p ${PKG_PATH}/usr/share/applications mkdir -p ${PKG_PATH}/usr/share/pixmaps echo "复制 Qt 运行时环境..." -cp -rfd ${QT_PKG_PATH}/ext ${PKG_PATH}/opt/firefly_qt5.15 -cp ${QT_PKG_PATH}/target_qtEnv.sh ${PKG_PATH}/etc/profile.d/ +cp -rfd ${QT_PKG_PATH}/ext ${PKG_PATH}/opt/firefly_qt5.15 +cp ${QT_PKG_PATH}/target_qtEnv.sh ${PKG_PATH}/etc/profile.d/ + +# 复制 Qt 库文件 +for libfile in ${QT_LIB_PATH}/*.so*; do + # 获取文件名用于比较 + filename=$(basename "$libfile") + + # 跳过 LLVM、flite、clang 和 X11 相关库文件 + if [[ "$filename" == *icu* ]]; then + # 复制其他库文件,保持符号链接 + cp -rfd "$libfile" ${PKG_PATH}/opt/sysroot/lib/ + continue + fi + +done echo "复制依赖库文件..." #depend @@ -77,11 +92,11 @@ cp ${CODE_PATH}/SDK/VzNLSDK/Arm/aarch64/*.so ${PKG_PATH}/usr/lib/ echo "复制应用程序主文件..." #APP -cp ${CODE_PATH}/GrabBagPrj/buildarm/GrabBagApp/GrabBagApp ${PKG_PATH}/usr/local/bin/ +cp ${CODE_PATH}/GrabBagPrj/buildarm/App/GrabBag/GrabBagApp/GrabBagApp ${PKG_PATH}/usr/local/bin/ echo "复制应用程序图标..." #LOGO -cp ${CODE_PATH}/GrabBagApp/resource/logo.png ${PKG_PATH}/usr/share/pixmaps/grabbag.png +cp ${CODE_PATH}/App/GrabBag/GrabBagApp/resource/logo.png ${PKG_PATH}/usr/share/pixmaps/grabbag.png echo "生成桌面自启动配置文件..." #desktop autostart configuration @@ -122,6 +137,7 @@ echo "配置 GrabBag 应用程序..." # 设置库文件路径 echo "/usr/lib" > /etc/ld.so.conf.d/grabbag.conf +echo "/opt/sysroot/lib/" >> /etc/ld.so.conf.d/grabbag.conf ldconfig # 确保应用程序可执行 diff --git a/VrNets/VrTcpServer.pro b/VrNets/VrTcpServer.pro index ec1b209..b298640 100644 --- a/VrNets/VrTcpServer.pro +++ b/VrNets/VrTcpServer.pro @@ -1,21 +1,40 @@ QT += core network -TARGET = VrTcpServer TEMPLATE = lib CONFIG += c++11 staticlib # 源文件 SOURCES += \ - tcpServer/Src/VrTcpServer.cpp + TCPServer/Src/CYServerTask.cpp \ + TCPServer/Src/CYTCPServer.cpp # 头文件 HEADERS += \ - tcpServer/Inc/IVrTcpServer.h \ - tcpServer/Inc/VrTcpServer.h + TCPServer/Inc/IYTCPServer.h \ + TCPServer/_Inc/CYServerTask.h \ + TCPServer/_Inc/CYTCPServer.h # 包含路径 INCLUDEPATH += \ - tcpServer/Inc + TCPServer/Inc \ + TCPServer/_Inc + + +INCLUDEPATH += $$PWD/../VrCommon/Inc +INCLUDEPATH += $$PWD/../VrUtils/Inc + + +win32:CONFIG(debug, debug|release) { + LIBS += -L../VrNets/debug -lVrUtils +}else:win32:CONFIG(release, debug|release){ + LIBS += -L../VrNets/release -lVrUtils +}else:unix:!macx { + # Unix/Linux平台库链接(包括交叉编译) + LIBS += -L../VrUtils -lVrUtils + + # 添加系统库依赖 + LIBS += -lpthread +} # 安装路径 target.path = $$[QT_INSTALL_LIBS] @@ -24,4 +43,4 @@ INSTALLS += target # 头文件安装路径 headers.files = $$HEADERS headers.path = $$[QT_INSTALL_HEADERS]/VrTcpServer -INSTALLS += headers \ No newline at end of file +INSTALLS += headers diff --git a/VrNets/tcpClient/Inc/IVrTcpClient.h b/VrNets/tcpClient/Inc/IVrTcpClient.h deleted file mode 100644 index 4ce0175..0000000 --- a/VrNets/tcpClient/Inc/IVrTcpClient.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef IVRTCPCLIENT_H -#define IVRTCPCLIENT_H - -#include -#include -#include - -class IVrTcpClient : public QObject -{ - Q_OBJECT - -public: - explicit IVrTcpClient(QObject *parent = nullptr) : QObject(parent) {} - virtual ~IVrTcpClient() {} - - // 连接服务器 - virtual bool connectToServer(const QString &host, quint16 port) = 0; - virtual bool connectToServer(const QHostAddress &address, quint16 port) = 0; - - // 断开连接 - virtual void disconnectFromServer() = 0; - - // 发送数据 - virtual qint64 sendData(const QByteArray &data) = 0; - virtual qint64 sendData(const char *data, qint64 size) = 0; - - // 获取连接状态 - virtual bool isConnected() const = 0; - - // 获取服务器地址和端口 - virtual QHostAddress serverAddress() const = 0; - virtual quint16 serverPort() const = 0; - -signals: - // 连接状态变化信号 - void connected(); - void disconnected(); - void connectionError(const QString &errorString); - - // 数据接收信号 - void dataReceived(const QByteArray &data); - - // 数据发送信号 - void dataSent(qint64 bytesSent); - -protected: - // 设置连接状态(供子类使用) - void setConnected(bool connected) { m_connected = connected; } - - bool m_connected = false; - QHostAddress m_serverAddress; - quint16 m_serverPort = 0; -}; - -#endif // IVRTCPCLIENT_H diff --git a/VrNets/tcpClient/Inc/VrTcpClient.h b/VrNets/tcpClient/Inc/VrTcpClient.h deleted file mode 100644 index 185ef77..0000000 --- a/VrNets/tcpClient/Inc/VrTcpClient.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef VRTCPCLIENT_H -#define VRTCPCLIENT_H - -#include "IVrTcpClient.h" -#include -#include - -class VrTcpClient : public IVrTcpClient -{ - Q_OBJECT - -public: - explicit VrTcpClient(QObject *parent = nullptr); - ~VrTcpClient() override; - - // 接口实现 - bool connectToServer(const QString &host, quint16 port) override; - bool connectToServer(const QHostAddress &address, quint16 port) override; - void disconnectFromServer() override; - qint64 sendData(const QByteArray &data) override; - qint64 sendData(const char *data, qint64 size) override; - bool isConnected() const override; - QHostAddress serverAddress() const override; - quint16 serverPort() const override; - - // 额外功能:自动重连 - void setAutoReconnect(bool autoReconnect); - bool autoReconnect() const; - - void setReconnectInterval(int msec); - int reconnectInterval() const; - -private: - QTcpSocket *m_socket; - QTimer *m_reconnectTimer; - bool m_autoReconnect = false; -}; - -#endif // VRTCPCLIENT_H diff --git a/VrNets/tcpClient/Src/VrTcpClient.cpp b/VrNets/tcpClient/Src/VrTcpClient.cpp deleted file mode 100644 index a01f4e5..0000000 --- a/VrNets/tcpClient/Src/VrTcpClient.cpp +++ /dev/null @@ -1,150 +0,0 @@ -#include "VrTcpClient.h" -#include -#include -#include - -VrTcpClient::VrTcpClient(QObject *parent) - : IVrTcpClient(parent) - , m_socket(new QTcpSocket(this)) - , m_reconnectTimer(new QTimer(this)) -{ - // 设置重连定时器 - m_reconnectTimer->setInterval(5000); // 5秒重连间隔 - m_reconnectTimer->setSingleShot(true); - - // 连接信号槽 - connect(m_socket, &QTcpSocket::connected, this, [this]() { - setConnected(true); - m_reconnectTimer->stop(); - emit connected(); - }); - - connect(m_socket, &QTcpSocket::disconnected, this, [this]() { - setConnected(false); - emit disconnected(); - - // 如果设置了自动重连,则启动重连定时器 - if (m_autoReconnect) { - m_reconnectTimer->start(); - } - }); - - connect(m_socket, QOverload::of(&QTcpSocket::errorOccurred), - this, [this](QAbstractSocket::SocketError error) { - setConnected(false); - emit connectionError(m_socket->errorString()); - }); - - connect(m_socket, &QTcpSocket::readyRead, this, [this]() { - QByteArray data = m_socket->readAll(); - if (!data.isEmpty()) { - emit dataReceived(data); - } - }); - - connect(m_socket, &QTcpSocket::bytesWritten, this, &IVrTcpClient::dataSent); - - connect(m_reconnectTimer, &QTimer::timeout, this, [this]() { - if (m_autoReconnect && !m_serverAddress.isNull() && m_serverPort > 0) { - connectToServer(m_serverAddress, m_serverPort); - } - }); -} - -VrTcpClient::~VrTcpClient() -{ - disconnectFromServer(); -} - -bool VrTcpClient::connectToServer(const QString &host, quint16 port) -{ - m_serverAddress = QHostAddress(host); - m_serverPort = port; - - if (m_serverAddress.isNull()) { - // 如果是主机名,直接连接 - m_socket->connectToHost(host, port); - return true; - } else { - return connectToServer(m_serverAddress, port); - } -} - -bool VrTcpClient::connectToServer(const QHostAddress &address, quint16 port) -{ - m_serverAddress = address; - m_serverPort = port; - - if (isConnected()) { - disconnectFromServer(); - } - - m_socket->connectToHost(address, port); - return true; -} - -void VrTcpClient::disconnectFromServer() -{ - m_reconnectTimer->stop(); - if (m_socket->state() != QAbstractSocket::UnconnectedState) { - m_socket->disconnectFromHost(); - if (m_socket->state() == QAbstractSocket::ConnectedState) { - m_socket->waitForDisconnected(3000); - } - } - setConnected(false); -} - -qint64 VrTcpClient::sendData(const QByteArray &data) -{ - if (!isConnected()) { - return -1; - } - return m_socket->write(data); -} - -qint64 VrTcpClient::sendData(const char *data, qint64 size) -{ - if (!isConnected()) { - return -1; - } - return m_socket->write(data, size); -} - -bool VrTcpClient::isConnected() const -{ - return m_connected && m_socket->state() == QAbstractSocket::ConnectedState; -} - -QHostAddress VrTcpClient::serverAddress() const -{ - return m_serverAddress; -} - -quint16 VrTcpClient::serverPort() const -{ - return m_serverPort; -} - -void VrTcpClient::setAutoReconnect(bool autoReconnect) -{ - m_autoReconnect = autoReconnect; - if (!autoReconnect) { - m_reconnectTimer->stop(); - } -} - -bool VrTcpClient::autoReconnect() const -{ - return m_autoReconnect; -} - -void VrTcpClient::setReconnectInterval(int msec) -{ - m_reconnectTimer->setInterval(msec); -} - -int VrTcpClient::reconnectInterval() const -{ - return m_reconnectTimer->interval(); -} diff --git a/VrNets/tcpClient/Test/test_tcp_client.cpp b/VrNets/tcpClient/Test/test_tcp_client.cpp deleted file mode 100644 index 07d2bb6..0000000 --- a/VrNets/tcpClient/Test/test_tcp_client.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "../Inc/VrTcpClient.h" -#include -#include -#include - -int main(int argc, char *argv[]) -{ - QCoreApplication app(argc, argv); - - VrTcpClient client; - - // 连接信号槽 - QObject::connect(&client, &IVrTcpClient::connected, []() { - qDebug() << "Connected to server!"; - }); - - QObject::connect(&client, &IVrTcpClient::disconnected, []() { - qDebug() << "Disconnected from server!"; - }); - - QObject::connect(&client, &IVrTcpClient::connectionError, [](const QString &error) { - qDebug() << "Connection error:" << error; - }); - - QObject::connect(&client, &IVrTcpClient::dataReceived, [](const QByteArray &data) { - qDebug() << "Received data:" << data.toHex(); - }); - - // 设置自动重连 - client.setAutoReconnect(true); - client.setReconnectInterval(3000); - - // 连接到服务器(这里使用一个不存在的地址进行测试) - client.connectToServer("127.0.0.1", 12345); - - // 5秒后退出 - QTimer::singleShot(5000, &app, &QCoreApplication::quit); - - return app.exec(); -} \ No newline at end of file diff --git a/VrNets/tcpServer/CMakeLists.txt b/VrNets/tcpServer/CMakeLists.txt new file mode 100644 index 0000000..d7cd534 --- /dev/null +++ b/VrNets/tcpServer/CMakeLists.txt @@ -0,0 +1,8 @@ +INCLUDE_DIRECTORIES( + ./_Inc + ./Inc +) + +AUX_SOURCE_DIRECTORY(./Src SrcS) + +ADD_LIBRARY(VrTCPServer STATIC ${SrcS}) diff --git a/VrNets/tcpServer/Inc/IVrTcpServer.h b/VrNets/tcpServer/Inc/IVrTcpServer.h deleted file mode 100644 index d09f877..0000000 --- a/VrNets/tcpServer/Inc/IVrTcpServer.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef IVRTCPSERVER_H -#define IVRTCPSERVER_H - -#include -#include - -class IVrTcpServer : public QObject -{ - Q_OBJECT - -public: - explicit IVrTcpServer(QObject *parent = nullptr); - ~IVrTcpServer() override; - - // 服务器接口 - virtual bool startServer(quint16 port) = 0; - virtual void stopServer() = 0; - virtual bool isListening() const = 0; - virtual quint16 serverPort() const = 0; - - // 数据发送接口 - virtual qint64 broadcastData(const QByteArray &data) = 0; - virtual qint64 broadcastData(const char *data, qint64 size) = 0; - -signals: - // 服务器状态信号 - void serverStarted(); - void serverStopped(); - void serverError(const QString &error); - - // 客户端连接信号 - void clientConnected(const QString &clientId); - void clientDisconnected(const QString &clientId); - void dataReceived(const QString &clientId, const QByteArray &data); - void dataSent(const QString &clientId, qint64 bytes); -}; - -#endif // IVRTCPSERVER_H \ No newline at end of file diff --git a/VrNets/tcpServer/Inc/IYTCPServer.h b/VrNets/tcpServer/Inc/IYTCPServer.h new file mode 100644 index 0000000..499c66c --- /dev/null +++ b/VrNets/tcpServer/Inc/IYTCPServer.h @@ -0,0 +1,56 @@ +#pragma once +#include + +#define MAX_CLIENT_NUM 10000 + +struct TCPClient +{ + int m_nFD; + void* m_Task; +}; + +enum TCPServerEventType +{ + TCP_EVENT_CLIENT_CONNECTED, // 客户端连接 + TCP_EVENT_CLIENT_DISCONNECTED, // 客户端断开 + TCP_EVENT_CLIENT_EXCEPTION // 客户端异常 +}; + +typedef std::function FunTCPServerRecv; +typedef std::function FunTCPServerEvent; + +class IYTCPServer +{ + +public: + virtual ~IYTCPServer() = default; + +public: + ///初始化socket + virtual bool Init(const int port, bool bOffNagle = false) = 0; + + ///初始化线程 + virtual bool Start(FunTCPServerRecv fRecv, bool bRecvSelfProtocol = false) = 0; + + ///设置事件回调 + virtual void SetEventCallback(FunTCPServerEvent fEvent) = 0; + + ///停止线程 + virtual bool Stop() = 0; + + ///发送消息 + virtual bool SendData(const TCPClient* pClient, const char* nBuf, const int nLen) = 0; + + ///发送给所有客户端 + virtual bool SendAllData(const char* nBuf, const int nLen) = 0; + + ///接收数据 + virtual bool RecvData(unsigned char** ppData, unsigned int* nLen) = 0; + + ///关闭 + virtual bool Close() = 0; + + +}; + +bool VrCreatYTCPServer(IYTCPServer** ppIYTCPServer); diff --git a/VrNets/tcpServer/Inc/VrTcpServer.h b/VrNets/tcpServer/Inc/VrTcpServer.h deleted file mode 100644 index a1452ac..0000000 --- a/VrNets/tcpServer/Inc/VrTcpServer.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef VRTCPSERVER_H -#define VRTCPSERVER_H - -#include "IVrTcpServer.h" -#include -#include -#include -#include - -class VrTcpServer : public IVrTcpServer -{ - Q_OBJECT - -public: - explicit VrTcpServer(QObject *parent = nullptr); - ~VrTcpServer() override; - - // 接口实现 - bool startServer(quint16 port) override; - void stopServer() override; - bool isListening() const override; - quint16 serverPort() const override; - - // 客户端管理 - QStringList getClientIds() const; - bool disconnectClient(const QString &clientId); - qint64 sendDataToClient(const QString &clientId, const QByteArray &data); - qint64 sendDataToClient(const QString &clientId, const char *data, qint64 size); - qint64 broadcastData(const QByteArray &data); - qint64 broadcastData(const char *data, qint64 size); - QHostAddress getClientAddress(const QString &clientId) const; - quint16 getClientPort(const QString &clientId) const; - -private slots: - void onNewConnection(); - void onClientDisconnected(); - void onClientReadyRead(); - void onClientBytesWritten(qint64 bytes); - void onServerError(QAbstractSocket::SocketError error); - -private: - QTcpServer *m_server; - QMap m_clients; - quint16 m_port; - QString generateClientId(QTcpSocket *socket); -}; - -#endif // VRTCPSERVER_H \ No newline at end of file diff --git a/VrNets/tcpServer/Src/CYServerTask.cpp b/VrNets/tcpServer/Src/CYServerTask.cpp new file mode 100644 index 0000000..8ccbb9d --- /dev/null +++ b/VrNets/tcpServer/Src/CYServerTask.cpp @@ -0,0 +1,263 @@ +#include "CYServerTask.h" + +CYServerTask::CYServerTask() + : m_maxSocket(INVALID_SOCKET) + , m_tTask(nullptr) + , m_bWork(false) + , m_eWorkStatus(WORK_INIT) + , m_fRecv(nullptr) + , m_fException(nullptr) + , m_bUseProtocol(false) +{ + m_vClient.clear(); + FD_ZERO(&m_fdRead); + FD_ZERO(&m_fdExp); + + m_pRecvBuf = new char[RECV_DATA_LEN]; + m_pProtocalHead = new ProtocolHead; +} + +CYServerTask::~CYServerTask() +{ + delete m_tTask; + m_tTask = nullptr; + + m_vClient.clear(); + FD_ZERO(&m_fdRead); + FD_ZERO(&m_fdExp); + + delete[] m_pRecvBuf; + delete m_pProtocalHead; +} + +bool CYServerTask::StartTask(FunTCPServerRecv fRecv, bool bRecvSelfProtocol) +{ + //1初始化线程 + m_bWork = true; + //2赋值回调函数 + m_fRecv = fRecv; + m_bUseProtocol = bRecvSelfProtocol; + + if (!m_tTask) + { + m_tTask = new std::thread(std::mem_fn(&CYServerTask::_OnProcessEvent), this); + m_tTask->detach(); + } + else + { + //发送信号进行初始化 + while (WORK_RUNING != m_eWorkStatus) + { + std::unique_lock lock(m_mutexWork); + m_cvWork.notify_one(); + } + } + + return true; +} + +void CYServerTask::SetExceptionCallback(std::function fException) +{ + m_fException = fException; +} + +bool CYServerTask::StopTask() +{ + m_bWork = false; + ///��������˿�ʼ�ŵȴ����� + if (m_tTask) + { + while (WORK_WAITSINGAL != m_eWorkStatus) + { + std::chrono::milliseconds milTime(10); + std::this_thread::sleep_for(milTime); + } + m_fRecv = nullptr; + delete m_tTask; + m_tTask = nullptr; + } + return true; +} + +///���ӿͻ��� +bool CYServerTask::AddClient(TCPClient * pClient) +{ + if(nullptr != pClient && m_vClient.size() < FD_SETSIZE) + { + std::lock_guard mLck(m_mClient); + //��¼Task�еĿͻ��� + m_vClient.push_back(pClient); + //���ӵ�select��fd������ + FD_SET(pClient->m_nFD, &m_fdRead); + FD_SET(pClient->m_nFD, &m_fdExp); + //�ҵ����FD + m_maxSocket = m_maxSocket > pClient->m_nFD ? m_maxSocket : pClient->m_nFD; + return true; + } + else + { + return false; + } +} + +///�Ƴ��ͻ��� +bool CYServerTask::DelClient(const TCPClient * pClient) +{ + bool bRet = false; + std::lock_guard mLck(m_mClient); + std::vector::iterator iter = m_vClient.begin(); + m_maxSocket = INVALID_SOCKET; + while (iter != m_vClient.end()) + { + if (*iter == pClient) + { + m_vClient.erase(iter); + FD_CLR(pClient->m_nFD, &m_fdRead); + FD_CLR(pClient->m_nFD, &m_fdExp); + bRet = true; + break; + } + } + + iter = m_vClient.begin(); + while (iter != m_vClient.end()) + { + //����ͳ�����ֵ + m_maxSocket = m_maxSocket > (*iter)->m_nFD ? m_maxSocket : (*iter)->m_nFD; + iter++; + } + return bRet; +} + +///��ȡTask�пͻ��˵���Ŀ +int CYServerTask::GetClientNum() +{ + return (int)m_vClient.size(); +} + +void CYServerTask::_OnProcessEvent() +{ + while (true) + { + if (!m_bWork) + { + m_eWorkStatus = WORK_WAITSINGAL; + std::unique_lock lock(m_mutexWork); + m_cvWork.wait(lock); + if (WORK_CLOSE == m_eWorkStatus) + { + break; + } + else + { + m_eWorkStatus = WORK_RUNING; + } + } + else + { + if (m_vClient.empty()) + { + std::chrono::milliseconds milTime(1); + std::this_thread::sleep_for(milTime); + continue; + } + + + ///��ʱ�ͻ���vector + std::vector vTCPClient; + fd_set fdRead; + fd_set fdExp; + { + std::unique_lock lock(m_mClient); + vTCPClient = m_vClient; + fdRead = m_fdRead; + fdExp = m_fdExp; + } + + struct timeval sWaitTime = { 0, 1000 }; + int nCount = select((int)m_maxSocket + 1, &fdRead, nullptr, &fdExp, &sWaitTime); + + if (nCount <= 0) + { + continue; + } + + for (int i = (int)vTCPClient.size() - 1; i >= 0; i--) + { + TCPClient* tmpClient = vTCPClient[i]; + if (FD_ISSET(tmpClient->m_nFD, &fdRead)) + { + //���ܲ��ص� + if (!_OnProcessData(tmpClient)) + { + if (m_fException) + { + m_fException(tmpClient); + } + } + } + } + } + } + m_eWorkStatus = WORK_EXIT; +} + +bool CYServerTask::_OnProcessData(TCPClient* pClient) +{ + const int nRecvLen = RECV_DATA_LEN; + + //��Э����� + if(!m_bUseProtocol) + { + int nCount = recv(pClient->m_nFD, m_pRecvBuf, nRecvLen, 0); + + if (nCount > 0 && m_fRecv) + { + m_fRecv(pClient, m_pRecvBuf, nCount); + } + return nCount > 0; + } + + //Э����� + int nAllDataLen = 0; + int recv_len = 0; + int nRet = 0; + + int nDataAddr = 6 * sizeof(int); + + //recv head + do + { + if ((recv_len = recv(pClient->m_nFD, (char *)(m_pProtocalHead) + nAllDataLen, nDataAddr - nAllDataLen, 0)) <= 0) + { + printf("read head failed \n"); + return false; + } + nAllDataLen += recv_len; + } while (nAllDataLen < nDataAddr); + + + nAllDataLen = 0; + //recv data + while (nAllDataLen < m_pProtocalHead->nLen) + { + recv_len = recv(pClient->m_nFD, (char *)(m_pProtocalHead) + nDataAddr + nAllDataLen, + m_pProtocalHead->nLen - nAllDataLen, 0); + if (recv_len <= 0) + { + printf("read data len : %d failed [%d]\n", m_pProtocalHead->nLen - nAllDataLen, recv_len); + return false; + } + + nAllDataLen += recv_len; + } + nAllDataLen = 0; + + if (m_fRecv) + { + m_fRecv(pClient, (char *)m_pProtocalHead, m_pProtocalHead->nLen + nDataAddr); + } + + //printf("cmd = %x len = %d \n", protocol.nCmd, protocol.nLen); + return 0 == nRet; +} diff --git a/VrNets/tcpServer/Src/CYTCPServer.cpp b/VrNets/tcpServer/Src/CYTCPServer.cpp new file mode 100644 index 0000000..35ae787 --- /dev/null +++ b/VrNets/tcpServer/Src/CYTCPServer.cpp @@ -0,0 +1,420 @@ +#include "CYTCPServer.h" +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif // _WIN32 + +CYTCPServer::CYTCPServer() + : m_eWorkStats(WORK_INIT) + , m_nSocket(INVALID_SOCKET) + , m_bWork(false) + , m_fRecv(nullptr) + , m_fEvent(nullptr) + , m_bCreateRecv(false) + , m_bOffNagle(false) +{ + m_vTCPClient.clear(); +} + +CYTCPServer::~CYTCPServer() +{ + Close(); +} + +///��ʼ��socket +bool CYTCPServer::Init(const int port, bool bOffNagle) +{ + bool bRet = false; +#ifdef _WIN32 + WORD ver = MAKEWORD(2, 2); + WSADATA data; + WSAStartup(ver, &data); +#endif // _WIN32 + + m_nSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + if (m_nSocket != INVALID_SOCKET) + { + bRet = true; + sockaddr_in sSockAddr; + sSockAddr.sin_family = AF_INET; + sSockAddr.sin_port = htons(port);//host to net unsigned short +#ifdef _WIN32 + sSockAddr.sin_addr.S_un.S_addr = INADDR_ANY; +#else + sSockAddr.sin_addr.s_addr = INADDR_ANY; + memset(sSockAddr.sin_zero, 0, 8); + + int opt = SO_REUSEADDR; + int len = sizeof(opt); + setsockopt(m_nSocket, SOL_SOCKET, SO_REUSEADDR, &opt, len); + + if(bOffNagle) + { + int on = 1; //�Ƿ��nagle�㷨�� //1 �������͵���tcp�� 0����ӳٷ���tcp�� + setsockopt(m_nSocket, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on)); + } + +#endif // _WIN32 + + int nRet = bind(m_nSocket, (struct sockaddr*)&sSockAddr, sizeof(sockaddr_in)); + if (SOCKET_ERROR != nRet) + { + nRet = listen(m_nSocket, 50); + if (SOCKET_ERROR == nRet) + { + bRet = false; + } + } + else + { + printf("bind %d failed \n", port); + bRet = false; + } + + if (!bRet) + { +#ifdef _WIN32 + closesocket(m_nSocket); +#else + close(m_nSocket); +#endif + m_nSocket = INVALID_SOCKET; + } + } + else + { + bRet = false; + } + return bRet; +} + +bool CYTCPServer::Start(FunTCPServerRecv fRecv, bool bRecvSelfProtocol) +{ + //1����ʼ���� + m_bWork = true; + //2����ֵ�ص����� + m_fRecv = fRecv; + + //3��ע��ص����������������̣߳���ص�����Ϊnull ����Ҫ��������RecvData�ӿ� + if (!m_bCreateRecv) + { + //��������������߳̽������ظ����� + std::thread tLinkThread(std::bind(&CYTCPServer::_OnMonitorLink, this)); + tLinkThread.detach(); + //�״ν���ֱ�ӽ���runing ״̬ + m_eWorkStats = WORK_RUNING; + m_bCreateRecv = true; + + ///���������Task + std::lock_guard oLck(m_mVectorTask); + for (int i = (int)floor(MAX_CLIENT_NUM / FD_SETSIZE); i >= 0; i--) + { + CYServerTask* pServerTask = new CYServerTask(); + m_vServerTask.push_back(pServerTask); + pServerTask->StartTask(fRecv, bRecvSelfProtocol); + + // 设置异常处理回调,指向CYTCPServer的_Exception方法 + pServerTask->SetExceptionCallback([this](const TCPClient* pClient) { + this->_Exception(pClient); + }); + } + } + else + { + //�����źŽ��п�ʼ + while (WORK_RUNING != m_eWorkStats) + { + std::unique_lock lock(m_mutexRecv); + m_cvRecv.notify_one(); + } + } + return true; +} + +void CYTCPServer::SetEventCallback(FunTCPServerEvent fEvent) +{ + m_fEvent = fEvent; +} + +bool CYTCPServer::Stop() +{ + m_bWork = false; + ///��������˿�ʼ�ŵȴ����� + if(m_bCreateRecv) + { + while (WORK_WAITSINGAL != m_eWorkStats) + { + std::chrono::milliseconds milTime(1); + std::this_thread::sleep_for(milTime); + } + m_fRecv = nullptr; + + ///�رշ����Task + std::lock_guard oLck(m_mVectorTask); + std::vector::iterator iter = m_vServerTask.begin(); + while(iter != m_vServerTask.end()) + { + (*iter)->StopTask(); + iter = m_vServerTask.erase(iter); + } + } + m_bCreateRecv = false; + return true; +} + +///������Ϣ +bool CYTCPServer::SendData(const TCPClient* pClient, const char* nBuf, const int nLen) +{ + if (nullptr == pClient || pClient->m_nFD == INVALID_SOCKET) + { + return false; + } + + std::lock_guard mSocketLock(m_mSocketMutex); + + bool bRet = false; + std::vector vTCPClient; + { + std::unique_lock lock(m_mVectorSocket); + vTCPClient = m_vTCPClient; + } + + for (int i = (int)vTCPClient.size() - 1; i >= 0; i--) + { + if (pClient->m_nFD == vTCPClient[i]->m_nFD) + { +#if 0 + int* intBuf = (int *)nBuf; + printf("send : %x len : %d Alleln : %d\n", *(intBuf + 2), *(intBuf + 4), nLen); +#endif + int nSendLen = 0; + while (nSendLen < nLen) + { + int len = send(pClient->m_nFD, nBuf + nSendLen, nLen - nSendLen, 0); + if (len <= 0) + { + bRet = false; + break; + } + nSendLen += len; + } + + if (nSendLen == nLen) + { + bRet = true; + } + } + } + return bRet; +} + +///���͸����пͻ��� +bool CYTCPServer::SendAllData(const char* nBuf, const int nLen) +{ + bool bRet = false; + std::vector vTCPClient; + { + std::unique_lock lock(m_mVectorSocket); + vTCPClient = m_vTCPClient; + } + + for (int i = (int)vTCPClient.size() - 1; i >= 0; i--) + { + bRet = SendData(vTCPClient[i], nBuf, nLen); + } + return bRet; +} + +///�������� +bool CYTCPServer::RecvData(unsigned char** ppData, unsigned int* nLen) +{ + bool bRet = true; + + + return bRet; +} + +bool CYTCPServer::Close() +{ + if (INVALID_SOCKET != m_nSocket) + { + if (WORK_RUNING == m_eWorkStats) + { + Stop(); + } + +#ifdef _WIN32 + closesocket(m_nSocket); + WSACleanup(); +#else + close(m_nSocket); +#endif + m_nSocket = INVALID_SOCKET; + + m_eWorkStats = WORK_CLOSE; + + while (m_eWorkStats != WORK_EXIT) + { + std::unique_lock lock(m_mutexRecv); + m_cvRecv.notify_one(); + + std::chrono::milliseconds milTime(1); + std::this_thread::sleep_for(milTime); + + } + } + return true; +} + +void CYTCPServer::_AddClient(const SOCKET nSocket, const sockaddr_in sClientAddr) +{ + printf("index : %d welcome [%d]: %s\n", (int)m_vTCPClient.size(), (int)nSocket, inet_ntoa(sClientAddr.sin_addr)); + TCPClient* pTCPClient = new TCPClient; + pTCPClient->m_nFD = (int)nSocket; + + { + std::unique_lock lock(m_mVectorSocket); + m_vTCPClient.push_back(pTCPClient); + } + { + std::lock_guard taskLock(m_mVectorTask); + std::vector::iterator iterMin = m_vServerTask.begin(); + std::vector::iterator iter = m_vServerTask.begin(); + while (iter != m_vServerTask.end()) + { + iterMin = (*iterMin)->GetClientNum() <= (*iter)->GetClientNum() ? iterMin : iter; + iter++; + } + + if (iterMin != m_vServerTask.end()) + { + pTCPClient->m_Task = (*iterMin); + (*iterMin)->AddClient(pTCPClient); + } + } + + // 触发客户端连接事件 + if (m_fEvent) + { + m_fEvent(pTCPClient, TCP_EVENT_CLIENT_CONNECTED); + } +} + +void CYTCPServer::_CloseClient(const TCPClient* pClient) +{ + std::unique_lock lock(m_mVectorSocket); + std::vector::iterator iter = m_vTCPClient.begin(); + while(iter != m_vTCPClient.end()) + { + if(*iter == pClient) + { + printf("client exit [%d]\n", (int)pClient->m_nFD); + + // 触发客户端断开连接事件 + if (m_fEvent) + { + m_fEvent(pClient, TCP_EVENT_CLIENT_DISCONNECTED); + } + + delete pClient; + m_vTCPClient.erase(iter); + break; + } + else + { + iter++; + } + } +} + +void CYTCPServer::_OnMonitorLink() +{ + //select + fd_set fdRead; + fd_set fdExp; + + while (true) + { + if (!m_bWork) + { + m_eWorkStats = WORK_WAITSINGAL; + std::unique_lock lock(m_mutexRecv); + m_cvRecv.wait(lock); + if (WORK_CLOSE == m_eWorkStats) + { + break; + } + else + { + m_eWorkStats = WORK_RUNING; + } + } + else + { + FD_ZERO(&fdRead); + FD_ZERO(&fdExp); + + FD_SET(m_nSocket, &fdRead); + FD_SET(m_nSocket, &fdExp); + + struct timeval sWaitTime = {0, 1000}; + int nCount = select((int)m_nSocket + 1, &fdRead, nullptr, &fdExp, &sWaitTime); + + if (nCount <= 0) + { + continue; + } + + if (FD_ISSET(m_nSocket, &fdRead)) + { + FD_CLR(m_nSocket, &fdRead); + sockaddr_in sClientAddr; + int nAddrLen = sizeof(sockaddr_in); + SOCKET nSocket = accept(m_nSocket, (sockaddr*)&sClientAddr, (socklen_t *)&nAddrLen); + + if (INVALID_SOCKET != nSocket) + { + _AddClient(nSocket, sClientAddr); + } + } + + } + } + m_eWorkStats = WORK_EXIT; +} + +/// �����쳣 +void CYTCPServer::_Exception(const TCPClient* pClient) +{ + // 触发客户端异常事件 + if (m_fEvent) + { + m_fEvent(pClient, TCP_EVENT_CLIENT_EXCEPTION); + } + + CYServerTask* p = (CYServerTask*)(pClient->m_Task); + p->DelClient(pClient); + _CloseClient(pClient); +} + +bool VrCreatYTCPServer(IYTCPServer** ppIYTCPServer) +{ + CYTCPServer* pYTCPServer = new CYTCPServer(); + *ppIYTCPServer = pYTCPServer; + return true; +} \ No newline at end of file diff --git a/VrNets/tcpServer/Src/VrTcpServer.cpp b/VrNets/tcpServer/Src/VrTcpServer.cpp deleted file mode 100644 index a10f4f6..0000000 --- a/VrNets/tcpServer/Src/VrTcpServer.cpp +++ /dev/null @@ -1,281 +0,0 @@ -#include "VrTcpServer.h" -#include -#include -#include -#include - -VrTcpServer::VrTcpServer(QObject *parent) - : IVrTcpServer(parent) - , m_server(new QTcpServer(this)) - , m_port(0) -{ - // 连接服务器信号 - connect(m_server, &QTcpServer::newConnection, this, &VrTcpServer::onNewConnection); - connect(m_server, &QTcpServer::acceptError, this, [this](QAbstractSocket::SocketError error) { - emit serverError(m_server->errorString()); - }); -} - -VrTcpServer::~VrTcpServer() -{ - stopServer(); -} - -bool VrTcpServer::startServer(quint16 port) -{ - if (m_server->isListening()) { - stopServer(); - } - - m_port = port; - bool result = m_server->listen(QHostAddress::Any, port); - - if (result) { - emit serverStarted(); - } else { - emit serverError(m_server->errorString()); - } - - return result; -} - -void VrTcpServer::stopServer() -{ - // 断开所有客户端连接 - for (auto it = m_clients.begin(); it != m_clients.end(); ++it) { - it.value()->disconnectFromHost(); - } - m_clients.clear(); - - // 停止服务器监听 - if (m_server->isListening()) { - m_server->close(); - emit serverStopped(); - } - - m_port = 0; -} - -bool VrTcpServer::isListening() const -{ - return m_server->isListening(); -} - -quint16 VrTcpServer::serverPort() const -{ - return m_port; -} - -QStringList VrTcpServer::getClientIds() const -{ - return m_clients.keys(); -} - -bool VrTcpServer::disconnectClient(const QString &clientId) -{ - if (!m_clients.contains(clientId)) { - return false; - } - - QTcpSocket *socket = m_clients[clientId]; - socket->disconnectFromHost(); - return true; -} - -qint64 VrTcpServer::sendDataToClient(const QString &clientId, const QByteArray &data) -{ - if (!m_clients.contains(clientId)) { - return -1; - } - - QTcpSocket *socket = m_clients[clientId]; - if (socket->state() != QAbstractSocket::ConnectedState) { - return -1; - } - - return socket->write(data); -} - -qint64 VrTcpServer::sendDataToClient(const QString &clientId, const char *data, qint64 size) -{ - if (!m_clients.contains(clientId)) { - return -1; - } - - QTcpSocket *socket = m_clients[clientId]; - if (socket->state() != QAbstractSocket::ConnectedState) { - return -1; - } - - return socket->write(data, size); -} - -QHostAddress VrTcpServer::getClientAddress(const QString &clientId) const -{ - if (!m_clients.contains(clientId)) { - return QHostAddress(); - } - - return m_clients[clientId]->peerAddress(); -} - -quint16 VrTcpServer::getClientPort(const QString &clientId) const -{ - if (!m_clients.contains(clientId)) { - return 0; - } - - return m_clients[clientId]->peerPort(); -} - -void VrTcpServer::onNewConnection() -{ - while (m_server->hasPendingConnections()) { - QTcpSocket *socket = m_server->nextPendingConnection(); - - // 生成客户端ID - QString clientId = generateClientId(socket); - - // 存储客户端连接 - m_clients[clientId] = socket; - - // 连接客户端信号 - connect(socket, &QTcpSocket::disconnected, this, &VrTcpServer::onClientDisconnected); - connect(socket, &QTcpSocket::readyRead, this, &VrTcpServer::onClientReadyRead); - connect(socket, &QTcpSocket::bytesWritten, this, &VrTcpServer::onClientBytesWritten); - - // 发送客户端连接信号 - emit clientConnected(clientId); - } -} - -void VrTcpServer::onClientDisconnected() -{ - QTcpSocket *socket = qobject_cast(sender()); - if (!socket) { - return; - } - - // 查找断开连接的客户端ID - QString clientId; - for (auto it = m_clients.begin(); it != m_clients.end(); ++it) { - if (it.value() == socket) { - clientId = it.key(); - break; - } - } - - if (!clientId.isEmpty()) { - m_clients.remove(clientId); - emit clientDisconnected(clientId); - } - - socket->deleteLater(); -} - -void VrTcpServer::onClientReadyRead() -{ - QTcpSocket *socket = qobject_cast(sender()); - if (!socket) { - return; - } - - // 查找客户端ID - QString clientId; - for (auto it = m_clients.begin(); it != m_clients.end(); ++it) { - if (it.value() == socket) { - clientId = it.key(); - break; - } - } - - if (!clientId.isEmpty()) { - QByteArray data = socket->readAll(); - if (!data.isEmpty()) { - emit dataReceived(clientId, data); - } - } -} - -void VrTcpServer::onClientBytesWritten(qint64 bytes) -{ - QTcpSocket *socket = qobject_cast(sender()); - if (!socket) { - return; - } - - // 查找客户端ID - QString clientId; - for (auto it = m_clients.begin(); it != m_clients.end(); ++it) { - if (it.value() == socket) { - clientId = it.key(); - break; - } - } - - if (!clientId.isEmpty()) { - emit dataSent(clientId, bytes); - } -} - -void VrTcpServer::onServerError(QAbstractSocket::SocketError error) -{ - Q_UNUSED(error) - QTcpSocket *socket = qobject_cast(sender()); - if (!socket) { - return; - } - - // 查找客户端ID - QString clientId; - for (auto it = m_clients.begin(); it != m_clients.end(); ++it) { - if (it.value() == socket) { - clientId = it.key(); - break; - } - } - - if (!clientId.isEmpty()) { - emit serverError(QString("Client %1 error: %2").arg(clientId, socket->errorString())); - } -} - -QString VrTcpServer::generateClientId(QTcpSocket *socket) -{ - // 使用UUID生成唯一的客户端ID - return QUuid::createUuid().toString(); -} - -qint64 VrTcpServer::broadcastData(const QByteArray &data) -{ - qint64 totalBytesWritten = 0; - - for (auto it = m_clients.begin(); it != m_clients.end(); ++it) { - QTcpSocket *socket = it.value(); - if (socket->state() == QAbstractSocket::ConnectedState) { - qint64 bytesWritten = socket->write(data); - if (bytesWritten > 0) { - totalBytesWritten += bytesWritten; - } - } - } - - return totalBytesWritten; -} - -qint64 VrTcpServer::broadcastData(const char *data, qint64 size) -{ - qint64 totalBytesWritten = 0; - - for (auto it = m_clients.begin(); it != m_clients.end(); ++it) { - QTcpSocket *socket = it.value(); - if (socket->state() == QAbstractSocket::ConnectedState) { - qint64 bytesWritten = socket->write(data, size); - if (bytesWritten > 0) { - totalBytesWritten += bytesWritten; - } - } - } - - return totalBytesWritten; -} diff --git a/VrNets/tcpServer/Test/test_tcp_server.cpp b/VrNets/tcpServer/Test/test_tcp_server.cpp deleted file mode 100644 index a7d586e..0000000 --- a/VrNets/tcpServer/Test/test_tcp_server.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include "../Inc/VrTcpServer.h" -#include -#include -#include - -int main(int argc, char *argv[]) -{ - QCoreApplication app(argc, argv); - - VrTcpServer server; - - // 连接信号槽 - QObject::connect(&server, &IVrTcpServer::serverStarted, []() { - qDebug() << "Server started!"; - }); - - QObject::connect(&server, &IVrTcpServer::serverStopped, []() { - qDebug() << "Server stopped!"; - }); - - QObject::connect(&server, &IVrTcpServer::serverError, [](const QString &error) { - qDebug() << "Server error:" << error; - }); - - QObject::connect(&server, &IVrTcpServer::clientConnected, [&server](const QString &clientId) { - qDebug() << "Client connected:" << clientId; - - // 发送欢迎消息给新连接的客户端 - QByteArray welcomeMessage = "Welcome to the server!"; - server.sendDataToClient(clientId, welcomeMessage); - }); - - QObject::connect(&server, &IVrTcpServer::clientDisconnected, [](const QString &clientId) { - qDebug() << "Client disconnected:" << clientId; - }); - - QObject::connect(&server, &IVrTcpServer::dataReceived, [&server](const QString &clientId, const QByteArray &data) { - qDebug() << "Received data from" << clientId << ":" << data.toHex(); - - // 将收到的数据广播给所有客户端 - QByteArray message = "Broadcast from " + clientId.toUtf8() + ": " + data; - server.broadcastData(message); - }); - - // 启动服务器 - if (!server.startServer(12345)) { - qDebug() << "Failed to start server"; - return -1; - } - - qDebug() << "Server listening on port 12345"; - - // 10秒后停止服务器并退出 - QTimer::singleShot(10000, [&server, &app]() { - server.stopServer(); - app.quit(); - }); - - return app.exec(); -} \ No newline at end of file diff --git a/VrNets/tcpServer/_Inc/CYServerTask.h b/VrNets/tcpServer/_Inc/CYServerTask.h new file mode 100644 index 0000000..c7a65c3 --- /dev/null +++ b/VrNets/tcpServer/_Inc/CYServerTask.h @@ -0,0 +1,115 @@ +#pragma once +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#include +#endif // !_WIN32 + +#include "IYTCPServer.h" + +typedef std::function FunTCPServerRecv; + +#define RECV_DATA_LEN 12 * 1024 * 1024 + +#ifdef _WIN32 + +#define FD_SETSIZE 1024 +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#include +#include + +#pragma comment(lib, "ws2_32.lib") + +#else +#define SOCKET int +#define INVALID_SOCKET (SOCKET)(~0) +#define SOCKET_ERROR (-1) +#endif + +enum WORK_STATUS +{ + WORK_INIT, + WORK_RUNING, + WORK_WAITSINGAL, + WORK_CLOSE, + WORK_EXIT, +}; + +class CYServerTask +{ +public: + CYServerTask(); + ~CYServerTask(); + + ///��ʼ���� +///开始线程 +///开始任务 + bool StartTask(FunTCPServerRecv fRecv, bool bRecvSelfProtocol = false); + + ///设置异常处理回调(给CYTCPServer使用) + void SetExceptionCallback(std::function fException); + + ///ֹͣ���� + bool StopTask(); + + ///���ӿͻ��� + bool AddClient(TCPClient* pClient); + + ///�Ƴ��ͻ��� + bool DelClient(const TCPClient* pClient); + + ///��ȡTask�пͻ��˵���Ŀ + int GetClientNum(); + +private: + void _OnProcessEvent(); + + bool _OnProcessData(TCPClient* pClient); + +private: + + ///客户端存储vector + std::mutex m_mClient; + std::vector m_vClient; + + ///select 监听 注释:和m_vClient使用同一把锁 + int m_maxSocket; + fd_set m_fdRead; + fd_set m_fdExp; + + ///线程管理 + std::thread* m_tTask; + bool m_bWork; + WORK_STATUS m_eWorkStatus; + + //工作信号 + std::mutex m_mutexWork; + std::condition_variable m_cvWork; + + //处理回调[接口] +private: + //处理回调[接口] + FunTCPServerRecv m_fRecv; + std::function m_fException; + char* m_pRecvBuf; + + std::atomic m_bUseProtocol; + + struct ProtocolHead + { + int nHead; + int nTime; + int nCmd; + int nDef; + int nLen; + int nTail; + char pData[RECV_DATA_LEN]; + }; + + ProtocolHead* m_pProtocalHead; + +}; diff --git a/VrNets/tcpServer/_Inc/CYTCPServer.h b/VrNets/tcpServer/_Inc/CYTCPServer.h new file mode 100644 index 0000000..53ee72a --- /dev/null +++ b/VrNets/tcpServer/_Inc/CYTCPServer.h @@ -0,0 +1,92 @@ +#pragma once +#include +#include +#include +#include "VrError.h" + +#ifndef _WIN32 +#include +#include +#endif // !_WIN32 + + +#include "IYTCPServer.h" +#include "CYServerTask.h" + +#define MAX_LINK_NUM 10000 + +class CYTCPServer : public IYTCPServer +{ +public: + CYTCPServer(); + ~CYTCPServer(); + +public: + ///初始化socket + virtual bool Init(const int port, bool bOffNagle = false); + + ///初始化线程 + virtual bool Start(FunTCPServerRecv fRecv, bool bRecvSelfProtocol = false); + + ///设置事件回调 + virtual void SetEventCallback(FunTCPServerEvent fEvent); + + ///停止线程 + virtual bool Stop(); + + ///发送消息 + virtual bool SendData(const TCPClient* pClient, const char* nBuf, const int nLen); + + ///发送给所有客户端 + virtual bool SendAllData(const char* nBuf, const int nLen); + + ///接收数据 + virtual bool RecvData(unsigned char** ppData, unsigned int* nLen); + + ///关闭 + virtual bool Close(); + +private: + + WORK_STATUS m_eWorkStats; + +private: + /// 回调函数 + FunTCPServerRecv m_fRecv; + FunTCPServerEvent m_fEvent; + + SOCKET m_nSocket; + + std::mutex m_mVectorSocket; + std::vector m_vTCPClient; + std::atomic m_bWork; + + std::mutex m_mSocketMutex; + + std::mutex m_mutexRecv; + std::condition_variable m_cvRecv; + + bool m_bCreateRecv; + + ///管理Task + std::mutex m_mVectorTask; + std::vector m_vServerTask; + + ///tcp 算法优化 + bool m_bOffNagle; + +private: + + /// 记录客户端 + void _AddClient(const SOCKET nSocket, const sockaddr_in sClientAddr); + + /// 关闭客户端 + void _CloseClient(const TCPClient* pClient); + + /// 监控连接 + void _OnMonitorLink(); + + /// 处理异常 + void _Exception(const TCPClient* pClient); + +};