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 0000000..6605ce3 --- /dev/null +++ b/App/GrabBag/Doc/拆垛机tcpip协议.docx @@ -0,0 +1,30 @@ +通信方式: + 工控机<->相机 tcp/ip + 工控机<->码垛机控制器 tcp/ip +视觉系统与码垛机之间要交互的数据格式如下: +功能 +设备名称 +方向 +设备名称 +发送字符串 +检测开始请求 +码垛机 +-> +视觉系统 +Trig +正常回复 +视觉系统 +-> +码垛机 +1,X,y,z,rz +检测开始请求 +码垛机 +-> +视觉系统 +Trig +拆垛完成(检测无料) +视觉系统 +-> +码垛机 +0 + diff --git a/App/GrabBag/GrabBag.pro b/App/GrabBag/GrabBag.pro new file mode 100644 index 0000000..84436ea --- /dev/null +++ b/App/GrabBag/GrabBag.pro @@ -0,0 +1,5 @@ +TEMPLATE = subdirs +# 拆包项目 +SUBDIRS += GrabBagConfig/GrabBagConfig.pro +SUBDIRS += GrabBagApp/GrabBagApp.pro +# SUBDIRS += GrabBagConfigCmd diff --git a/App/GrabBag/GrabBagApp/GrabBagApp.pro b/App/GrabBag/GrabBagApp/GrabBagApp.pro new file mode 100644 index 0000000..7315e5f --- /dev/null +++ b/App/GrabBag/GrabBagApp/GrabBagApp.pro @@ -0,0 +1,168 @@ +QT += core gui +QT += serialport network + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TEMPLATE = app + +CONFIG += c++17 +# Add /utf-8 flag only for MSVC builds to enforce UTF-8 encoding +win32-msvc { + QMAKE_CXXFLAGS += /utf-8 +} + + +# 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 + + +# 设置应用程序图标 +RC_ICONS = resource/logo.ico +INCLUDEPATH += $$PWD/Presenter/Inc +INCLUDEPATH += $$PWD/Utils/Inc + +INCLUDEPATH += $$PWD/../../../VrCommon/Inc +INCLUDEPATH += $$PWD/../../../VrUtils/Inc + +INCLUDEPATH += $$PWD/../../../Module/ModbusTCPServer/Inc +INCLUDEPATH += $$PWD/../../../Module/ShareMem/Inc + +INCLUDEPATH += $$PWD/../GrabBagConfig/Inc +INCLUDEPATH += $$PWD/../../../VrEyeDevice/Inc + +INCLUDEPATH += $$PWD/../../../VrNets/TCPServer/Inc + +SOURCES += \ + Presenter/Src/DetectPresenter.cpp \ + Presenter/Src/GrabBagPresenter.cpp \ + Presenter/Src/RobotProtocol.cpp \ + Presenter/Src/SerialProtocol.cpp \ + Presenter/Src/ConfigManager.cpp \ + Utils/Src/LaserDataLoader.cpp \ + Utils/Src/PathManager.cpp \ + Utils/Src/PointCloudImageUtils.cpp \ + 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/ProtocolCommon.h \ + Presenter/Inc/RobotProtocol.h \ + Presenter/Inc/SerialProtocol.h \ + Presenter/Inc/ConfigManager.h \ + Utils/Inc/LaserDataLoader.h \ + Utils/Inc/PathManager.h \ + IYGrabBagStatus.h \ + Utils/Inc/PointCloudImageUtils.h \ + Version.h \ + devstatus.h \ + dialogcamera.h \ + dialogcameralevel.h \ + dialogconfig.h \ + mainwindow.h \ + resultitem.h + +FORMS += \ + devstatus.ui \ + dialogcamera.ui \ + dialogcameralevel.ui \ + dialogconfig.ui \ + mainwindow.ui \ + resultitem.ui + +RESOURCES += \ + resources.qrc + + +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../../../VrNets/debug -lVrTcpServer +}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../../../VrNets/release -lVrTcpServer +}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../../../VrNets -lVrTcpServer + + # 添加系统库依赖 + LIBS += -lpthread +} + +#linux下的为unix ,windows下用的win32 + +INCLUDEPATH += ../../../SDK/VzNLSDK/_Inc +INCLUDEPATH += ../../../SDK/VzNLSDK/Inc + +win32:CONFIG(release, debug|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 += -lVzKerneld -lVzNLDetectd -lVzNLGraphicsd +} +else:unix:!macx: { + LIBS += -L$$PWD/../../../SDK/VzNLSDK/Arm/aarch64 + LIBS += -lVzEyeSecurityLoader-shared -lVzKernel -lVzNLDetect -lVzNLGraphics +} + + +# 算法 +INCLUDEPATH += ../../../SDK/bagPosition/Inc + +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 +} +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 +} +else:unix:!macx: { + LIBS += -L$$PWD/../../../SDK/bagPosition/Arm/aarch64 -lbagPositioning -lbaseAlgorithm + LIBS += -L$$PWD/../../../SDK/OpenCV320/Arm/aarch64 -lopencv_core -lopencv_imgproc -lopencv_highgui +} + +# 添加libmodbus依赖 +win32 { + LIBS += -lws2_32 + LIBS += Advapi32.lib +} + +# Default rules for deployment. +unix { + target.path = /usr/lib + # Link real-time library for POSIX shared memory functions + LIBS += -lrt +} +!isEmpty(target.path): INSTALLS += target + + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target diff --git a/GrabBagApp/IYGrabBagStatus.h b/App/GrabBag/GrabBagApp/IYGrabBagStatus.h similarity index 100% rename from GrabBagApp/IYGrabBagStatus.h rename to App/GrabBag/GrabBagApp/IYGrabBagStatus.h diff --git a/GrabBagApp/Presenter/Inc/ConfigManager.h b/App/GrabBag/GrabBagApp/Presenter/Inc/ConfigManager.h similarity index 100% rename from GrabBagApp/Presenter/Inc/ConfigManager.h rename to App/GrabBag/GrabBagApp/Presenter/Inc/ConfigManager.h diff --git a/GrabBagApp/Presenter/Inc/DetectPresenter.h b/App/GrabBag/GrabBagApp/Presenter/Inc/DetectPresenter.h similarity index 100% rename from GrabBagApp/Presenter/Inc/DetectPresenter.h rename to App/GrabBag/GrabBagApp/Presenter/Inc/DetectPresenter.h diff --git a/GrabBagApp/Presenter/Inc/GrabBagPresenter.h b/App/GrabBag/GrabBagApp/Presenter/Inc/GrabBagPresenter.h similarity index 88% rename from GrabBagApp/Presenter/Inc/GrabBagPresenter.h rename to App/GrabBag/GrabBagApp/Presenter/Inc/GrabBagPresenter.h index d2f0027..c1d618b 100644 --- a/GrabBagApp/Presenter/Inc/GrabBagPresenter.h +++ b/App/GrabBag/GrabBagApp/Presenter/Inc/GrabBagPresenter.h @@ -3,6 +3,8 @@ #include #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 0000000..c12f7b8 Binary files /dev/null and b/App/LapWeld/LapWeldApp/resource/camera_offline.png differ diff --git a/App/LapWeld/LapWeldApp/resource/camera_online.png b/App/LapWeld/LapWeldApp/resource/camera_online.png new file mode 100644 index 0000000..57bad65 Binary files /dev/null and b/App/LapWeld/LapWeldApp/resource/camera_online.png differ diff --git a/App/LapWeld/LapWeldApp/resource/close.png b/App/LapWeld/LapWeldApp/resource/close.png new file mode 100644 index 0000000..d4d6d0e Binary files /dev/null and b/App/LapWeld/LapWeldApp/resource/close.png differ diff --git a/App/LapWeld/LapWeldApp/resource/config_algo.png b/App/LapWeld/LapWeldApp/resource/config_algo.png new file mode 100644 index 0000000..fcbca88 Binary files /dev/null and b/App/LapWeld/LapWeldApp/resource/config_algo.png differ 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 0000000..e97a5b5 Binary files /dev/null and b/App/LapWeld/LapWeldApp/resource/config_algo_s.png differ diff --git a/App/LapWeld/LapWeldApp/resource/config_camera.png b/App/LapWeld/LapWeldApp/resource/config_camera.png new file mode 100644 index 0000000..906a442 Binary files /dev/null and b/App/LapWeld/LapWeldApp/resource/config_camera.png differ 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 0000000..a285091 Binary files /dev/null and b/App/LapWeld/LapWeldApp/resource/config_camera_level.png differ 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 0000000..aba0174 Binary files /dev/null and b/App/LapWeld/LapWeldApp/resource/config_camera_level_s.png differ diff --git a/App/LapWeld/LapWeldApp/resource/config_camera_s.png b/App/LapWeld/LapWeldApp/resource/config_camera_s.png new file mode 100644 index 0000000..ac891eb Binary files /dev/null and b/App/LapWeld/LapWeldApp/resource/config_camera_s.png differ 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 0000000..d90e55a Binary files /dev/null and b/App/LapWeld/LapWeldApp/resource/config_data_test.png differ 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 0000000..37b831e Binary files /dev/null and b/App/LapWeld/LapWeldApp/resource/config_data_test_s.png differ diff --git a/App/LapWeld/LapWeldApp/resource/config_model.png b/App/LapWeld/LapWeldApp/resource/config_model.png new file mode 100644 index 0000000..d62e017 Binary files /dev/null and b/App/LapWeld/LapWeldApp/resource/config_model.png differ diff --git a/App/LapWeld/LapWeldApp/resource/dialog_cancel.png b/App/LapWeld/LapWeldApp/resource/dialog_cancel.png new file mode 100644 index 0000000..c639362 Binary files /dev/null and b/App/LapWeld/LapWeldApp/resource/dialog_cancel.png differ diff --git a/App/LapWeld/LapWeldApp/resource/dialog_ok.png b/App/LapWeld/LapWeldApp/resource/dialog_ok.png new file mode 100644 index 0000000..4f75382 Binary files /dev/null and b/App/LapWeld/LapWeldApp/resource/dialog_ok.png differ diff --git a/App/LapWeld/LapWeldApp/resource/hide.png b/App/LapWeld/LapWeldApp/resource/hide.png new file mode 100644 index 0000000..d22235f Binary files /dev/null and b/App/LapWeld/LapWeldApp/resource/hide.png differ diff --git a/App/LapWeld/LapWeldApp/resource/logo.ico b/App/LapWeld/LapWeldApp/resource/logo.ico new file mode 100644 index 0000000..4c89d0a Binary files /dev/null and b/App/LapWeld/LapWeldApp/resource/logo.ico differ diff --git a/App/LapWeld/LapWeldApp/resource/logo.png b/App/LapWeld/LapWeldApp/resource/logo.png new file mode 100644 index 0000000..073c220 Binary files /dev/null and b/App/LapWeld/LapWeldApp/resource/logo.png differ diff --git a/App/LapWeld/LapWeldApp/resource/result_icon.png b/App/LapWeld/LapWeldApp/resource/result_icon.png new file mode 100644 index 0000000..c0df1f4 Binary files /dev/null and b/App/LapWeld/LapWeldApp/resource/result_icon.png differ diff --git a/App/LapWeld/LapWeldApp/resource/robot_offline.png b/App/LapWeld/LapWeldApp/resource/robot_offline.png new file mode 100644 index 0000000..adb8939 Binary files /dev/null and b/App/LapWeld/LapWeldApp/resource/robot_offline.png differ diff --git a/App/LapWeld/LapWeldApp/resource/robot_online.png b/App/LapWeld/LapWeldApp/resource/robot_online.png new file mode 100644 index 0000000..83c4259 Binary files /dev/null and b/App/LapWeld/LapWeldApp/resource/robot_online.png differ diff --git a/App/LapWeld/LapWeldApp/resource/start.png b/App/LapWeld/LapWeldApp/resource/start.png new file mode 100644 index 0000000..9db8efe Binary files /dev/null and b/App/LapWeld/LapWeldApp/resource/start.png differ diff --git a/App/LapWeld/LapWeldApp/resource/stop.png b/App/LapWeld/LapWeldApp/resource/stop.png new file mode 100644 index 0000000..c0f47b4 Binary files /dev/null and b/App/LapWeld/LapWeldApp/resource/stop.png differ 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); + +};