RodAndBarPositionApp 的姿态问题

This commit is contained in:
杰仔 2026-06-26 17:49:32 +08:00
parent 71abd70e2d
commit 9aa39794b7
29 changed files with 2578 additions and 68 deletions

View File

@ -6,7 +6,7 @@
#define RODANDBARPOSITION_APP_NAME "棒材定位"
#define RODANDBARPOSITION_VERSION_STRING "1.1.4"
#define RODANDBARPOSITION_BUILD_STRING "1"
#define RODANDBARPOSITION_BUILD_STRING "2"
#define RODANDBARPOSITION_FULL_VERSION_STRING "V" RODANDBARPOSITION_VERSION_STRING "_" RODANDBARPOSITION_BUILD_STRING
// 获取版本信息的便捷函数

View File

@ -1,4 +1,7 @@
# 1.1.4 2026-06-26
## build_2
1. 限定了欧拉角的输出范围
## build_1
1. 修改工具的参数配置

View File

@ -7,17 +7,21 @@
| 1 | 自动拆包 | GrabBag | 1.3.6.2 |
| 2 | 皮带撕裂 | BeltTearingServer | 2.0.8.1 |
| 3 | 孔洞检测 | HoleDetection | 1.1.6.2 |
| 4 | 棒材定位 | RodAndBarPosition | 1.1.0.1 |
| 5 | 车轮拱高测量 | WheelMeasure | 1.2.0.1 |
| 4 | 棒材定位 | RodAndBarPosition | 1.1.4.2 |
| 5 | 车轮拱高测量 | WheelMeasure | 1.2.2.2 |
| 6 | 定子定位 | StatorPosition | 1.0.0.1 |
| 7 | 焊接 | LapWeld | 1.0.0.1 |
| 8 | 工件定位 | Workpiece | 1.0.3.0 |
| 9 | 颗粒尺寸检测 | ParticleSize | 1.0.0.0 |
| 10 | 双目标记检测 | BinocularMarkServer | 1.0.0.4 |
| 11 | 铁路隧道槽道测量 | TunnelChannel | 1.0.0.3 |
| 12 | 螺杆定位 | ScrewPosition | 1.1.10.1 |
| 12 | 螺杆定位 | ScrewPosition | 1.2.4.1 |
| 13 | 包裹拆线位置定位 | BagThreadPosition | 1.0.0.4 |
| 14 | 工件孔定位 | WorkpieceHole | 1.1.5.1 |
| 14 | 工件孔定位 | WorkpieceHole | 1.1.6.1 |
| 15 | 轮胎孔定位 | TireHolePose | 1.0.1.1 |
| 16 | 坑孔定位 | HolePitPosition | 无 |
| 17 | 钢筋焊缝定位 | RodWeldSeam | 1.0.0.1 |
| 18 | 无人机螺杆检测服务端 | DroneScrewServer | 1.0.0.1 |
| 19 | 无人机螺杆检测控制端 | DroneScrewCtrlApp | 1.0.0.1 |
| 20 | 砂轮盘孔定位 | DiscHolePose | 1.0.0.1 |

View File

@ -2,7 +2,7 @@
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "点云查看器"
#define MyAppVersion "1.1.4"
#define MyAppVersion "1.1.6"
#define MyAppPublisher ""
#define MyAppURL ""
#define MyAppExeName "CloudView.exe"

View File

@ -27,7 +27,7 @@ Device.depends = Utils AppUtils
AppUtils.depends = Utils Module
App.depends = Utils VrNets Module Device AppUtils
Tools.depends = Module Utils Robot
Test.depends = Device Module
Test.depends = Utils Module Device
# Test 测试
Test.file = ../Test/Test.pro

44
GrabBagPrj/GrabBagPrj.sln Normal file
View File

@ -0,0 +1,44 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.33516.290
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ThreadPool", "..\Module\ThreadPool\ThreadPool.vcxproj", "{881F41B5-0FB2-4890-AB1F-F5F2013A22F7}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestThreadPool", "..\Test\TestThreadPool\TestThreadPool.vcxproj", "{0DBD887F-4AAC-4DB6-9706-4C6A43C3BB38}"
ProjectSection(ProjectDependencies) = postProject
{881F41B5-0FB2-4890-AB1F-F5F2013A22F7} = {881F41B5-0FB2-4890-AB1F-F5F2013A22F7}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{881F41B5-0FB2-4890-AB1F-F5F2013A22F7}.Debug|x64.ActiveCfg = Debug|x64
{881F41B5-0FB2-4890-AB1F-F5F2013A22F7}.Debug|x64.Build.0 = Debug|x64
{881F41B5-0FB2-4890-AB1F-F5F2013A22F7}.Debug|x86.ActiveCfg = Debug|Win32
{881F41B5-0FB2-4890-AB1F-F5F2013A22F7}.Debug|x86.Build.0 = Debug|Win32
{881F41B5-0FB2-4890-AB1F-F5F2013A22F7}.Release|x64.ActiveCfg = Release|x64
{881F41B5-0FB2-4890-AB1F-F5F2013A22F7}.Release|x64.Build.0 = Release|x64
{881F41B5-0FB2-4890-AB1F-F5F2013A22F7}.Release|x86.ActiveCfg = Release|Win32
{881F41B5-0FB2-4890-AB1F-F5F2013A22F7}.Release|x86.Build.0 = Release|Win32
{0DBD887F-4AAC-4DB6-9706-4C6A43C3BB38}.Debug|x64.ActiveCfg = Debug|x64
{0DBD887F-4AAC-4DB6-9706-4C6A43C3BB38}.Debug|x64.Build.0 = Debug|x64
{0DBD887F-4AAC-4DB6-9706-4C6A43C3BB38}.Debug|x86.ActiveCfg = Debug|Win32
{0DBD887F-4AAC-4DB6-9706-4C6A43C3BB38}.Debug|x86.Build.0 = Debug|Win32
{0DBD887F-4AAC-4DB6-9706-4C6A43C3BB38}.Release|x64.ActiveCfg = Release|x64
{0DBD887F-4AAC-4DB6-9706-4C6A43C3BB38}.Release|x64.Build.0 = Release|x64
{0DBD887F-4AAC-4DB6-9706-4C6A43C3BB38}.Release|x86.ActiveCfg = Release|Win32
{0DBD887F-4AAC-4DB6-9706-4C6A43C3BB38}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9CA02FFA-9326-4C71-A14C-B54AB2F579D9}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,23 @@
; TireHolePose Inno Setup 脚本
; 轮胎孔定位应用程序打包配置
[Setup]
AppName=轮胎孔定位
AppVersion=1.0.0
AppPublisher=ShangGu
DefaultDirName={pf}\TireHolePose
DefaultGroupName=轮胎孔定位
OutputBaseFilename=TireHolePose_Setup
Compression=lzma
SolidCompression=yes
UninstallDisplayIcon={app}\TireHolePoseApp.exe
[Files]
Source: "buildwin\TireHolePose\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
[Icons]
Name: "{group}\轮胎孔定位"; Filename: "{app}\TireHolePoseApp.exe"
Name: "{group}\卸载轮胎孔定位"; Filename: "{uninstallexe}"
[Run]
Filename: "{app}\TireHolePoseApp.exe"; Description: "运行轮胎孔定位"; Flags: nowait postinstall skipifsilent

View File

@ -110,8 +110,10 @@ fi
JOBS="${JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 8)}"
echo "=========================================="
echo "一次性编译,并行度 ${JOBS}"
echo "(若内存不足出现 cc1plus 被 Killed降低并行度重试JOBS=4 ./buildArmPrj.sh 或 JOBS=2 ./buildArmPrj.sh"
echo "执行 make -j${JOBS}"
# 一次性编译全部目标(并行度由 JOBS 控制,默认取 CPU 核心数)
BUILD_RESULT=0
make -j"${JOBS}" || BUILD_RESULT=$?

View File

@ -1,7 +1,8 @@
@echo off
setlocal
set PRJ_PATH=c:\project\QT\GrabBag\GrabBagPrj
set PRJ_PATH=%~dp0
set PRJ_PATH=%PRJ_PATH:~0,-1%
if not exist "%PRJ_PATH%\buildwin\AuthRegister" mkdir "%PRJ_PATH%\buildwin\AuthRegister"

View File

@ -1,7 +1,8 @@
@echo off
setlocal
set PRJ_PATH=c:\project\QT\GrabBag\GrabBagPrj
set PRJ_PATH=%~dp0
set PRJ_PATH=%PRJ_PATH:~0,-1%
if not exist "%PRJ_PATH%\buildwin\BeltTearingApp" mkdir "%PRJ_PATH%\buildwin\BeltTearingApp"

View File

@ -1,7 +1,8 @@
@echo off
setlocal
set PRJ_PATH=c:\project\QT\GrabBag\GrabBagPrj
set PRJ_PATH=%~dp0
set PRJ_PATH=%PRJ_PATH:~0,-1%
cd /d "%PRJ_PATH%\buildwin\BinocularMarkTest"

View File

@ -1,7 +1,8 @@
@echo off
setlocal
set PRJ_PATH=c:\project\QT\GrabBag\GrabBagPrj
set PRJ_PATH=%~dp0
set PRJ_PATH=%PRJ_PATH:~0,-1%
if not exist "%PRJ_PATH%\buildwin\CalibView" mkdir "%PRJ_PATH%\buildwin\CalibView"

View File

@ -1,7 +1,8 @@
@echo off
setlocal
set PRJ_PATH=c:\project\QT\GrabBag\GrabBagPrj
set PRJ_PATH=%~dp0
set PRJ_PATH=%PRJ_PATH:~0,-1%
if not exist "%PRJ_PATH%\buildwin\CloudView" mkdir "%PRJ_PATH%\buildwin\CloudView"

View File

@ -1,7 +1,8 @@
@echo off
setlocal
set PRJ_PATH=c:\project\QT\GrabBag\GrabBagPrj
set PRJ_PATH=%~dp0
set PRJ_PATH=%PRJ_PATH:~0,-1%
if not exist "%PRJ_PATH%\buildwin\ControlApp" mkdir "%PRJ_PATH%\buildwin\ControlApp"

View File

@ -0,0 +1,42 @@
@echo off
setlocal
REM project root = parent of this script's directory
cd /d "%~dp0.."
set PRJ_PATH=%cd%
set PUB_PATH=%PRJ_PATH%\Publish\ThreadPool
echo ========================================
echo pkg ThreadPool
echo ========================================
if exist "%PUB_PATH%" (
echo [WARN] dir already exists: %PUB_PATH%
echo [WARN] please delete it manually, then re-run.
pause
exit /b 1
)
mkdir "%PUB_PATH%"
REM 1. copy ThreadPool source (keep Inc / _Inc / Src structure)
echo [copy] ThreadPool source...
xcopy "%PRJ_PATH%\Module\ThreadPool\Inc\*.h" "%PUB_PATH%\Inc\" /Y /I /Q
xcopy "%PRJ_PATH%\Module\ThreadPool\_Inc\*.h" "%PUB_PATH%\_Inc\" /Y /I /Q
xcopy "%PRJ_PATH%\Module\ThreadPool\Src\*.cpp" "%PUB_PATH%\Src\" /Y /I /Q
REM 2. copy demo
echo [copy] demo...
xcopy "%PRJ_PATH%\Test\TestThreadPool\main.cpp" "%PUB_PATH%\demo\" /Y /I /Q
REM 3. copy project files
echo [copy] project files...
xcopy "%PRJ_PATH%\Module\ThreadPool\ThreadPool.pro" "%PUB_PATH%\" /Y /I /Q
xcopy "%PRJ_PATH%\Module\ThreadPool\ThreadPool.vcxproj" "%PUB_PATH%\" /Y /I /Q
xcopy "%PRJ_PATH%\Module\ThreadPool\ThreadPool.vcxproj.filters" "%PUB_PATH%\" /Y /I /Q
echo ========================================
echo done: %PUB_PATH%
echo ========================================
dir /s /b "%PUB_PATH%"
endlocal

View File

@ -0,0 +1,25 @@
@echo off
setlocal
set PRJ_PATH=%~dp0
set PRJ_PATH=%PRJ_PATH:~0,-1%
if not exist "%PRJ_PATH%\buildwin\TireHolePose" mkdir "%PRJ_PATH%\buildwin\TireHolePose"
cd /d "%PRJ_PATH%\buildwin\TireHolePose"
powershell -Command "Remove-Item * -Recurse -Force"
copy "..\..\build\App\TireHolePose\TireHolePoseApp\release\TireHolePoseApp.exe" .\
"C:\tools\Qt\5.15.2\msvc2019_64\bin\windeployqt.exe" "TireHolePoseApp.exe"
cd ..\..
set ISCC_PATH=C:\Program Files (x86)\Inno Setup 6\ISCC.exe
"%ISCC_PATH%" "TireHolePose.iss"
echo "finish"
endlocal

View File

@ -0,0 +1,52 @@
@echo off
setlocal
REM ============================================================
REM WDRemoteReceiver 模块测试工具 - Windows 打包脚本
REM
REM 输出:%PRJ_PATH%\buildwin\WDRemoteReceiverTest\
REM ============================================================
set PRJ_PATH=%~dp0
set PRJ_PATH=%PRJ_PATH:~0,-1%
set OUT_DIR=%PRJ_PATH%\buildwin\WDRemoteReceiverTest
set BUILD_DIR=%PRJ_PATH%\build\Test\WDRemoteReceiverTest\release
set WD_RECEIVER_DIR=%PRJ_PATH%\build\Module\WDRemoteReceiver\release
if not exist "%OUT_DIR%" mkdir "%OUT_DIR%"
cd /d "%OUT_DIR%"
REM 1) 拷贝可执行
copy "%BUILD_DIR%\WDRemoteReceiverTest.exe" .\
if errorlevel 1 (
echo [ERROR] not found: %BUILD_DIR%\WDRemoteReceiverTest.exe
exit /b 1
)
REM 2) Qt 依赖部署
REM 2) WDRemoteReceiver runtime
copy "%WD_RECEIVER_DIR%\WDRemoteReceiver.dll" .\
if errorlevel 1 (
echo [ERROR] not found: %WD_RECEIVER_DIR%\WDRemoteReceiver.dll
exit /b 1
)
"C:\tools\Qt\5.15.2\msvc2019_64\bin\windeployqt.exe" "WDRemoteReceiverTest.exe"
REM 3) 拷贝 ZeroMQ Runtime
copy "%PRJ_PATH%\..\SDK\ZeroMQ\Windows\x64\Release\libzmq*.dll" .\
REM 4) 拷贝 Protobuf Runtime如果需要
REM 5) OpenCV runtime used by WDRemoteReceiver
copy "%PRJ_PATH%\..\SDK\OpenCV320\Windows\vc14\Release\opencv_world320.dll" .\
copy "%PRJ_PATH%\..\SDK\Protobuf_3_20\Windows\x64\Release\libprotobuf*.dll" .\ 2>nul
cd /d "%PRJ_PATH%"
echo.
echo === WDRemoteReceiverTest Windows 打包完成 ===
echo 输出目录: %OUT_DIR%
echo.
endlocal

View File

@ -27,6 +27,21 @@ _array_contains() {
_trim_leading_spaces() { printf '%s' "${1#"${1%%[![:space:]]*}"}"; }
# 将逗号分隔的多条 spec 字符串拆分到指定数组里(空段忽略,自动去前导空格)
# 使用 read -ra 避免对 spec 中的 glob (*) 触发路径名展开
# 用法: _split_csv_specs "<csv>" out_array
_split_csv_specs() {
local _csv=$1
local -n _out=$2
local _items _item _trimmed
[ -z "$_csv" ] && return 0
IFS=',' read -ra _items <<< "$_csv"
for _item in "${_items[@]}"; do
_trimmed="$(_trim_leading_spaces "$_item")"
[ -n "$_trimmed" ] && _out+=("$_trimmed")
done
}
set_project_meta() { PROJECT_META["$1:$2"]=${3-}; }
get_project_meta() { printf '%s' "${PROJECT_META["$1:$2"]:-}"; }
@ -71,6 +86,7 @@ load_package_project_meta() {
PACKAGE_SERVICE_DEST_REL="$(get_project_meta "$project" package_service_dest_rel)"
PACKAGE_LD_CONF_EXTRA_LINES="$(get_project_meta "$project" package_ld_conf_extra_lines)"
PACKAGE_OPENCV_PROFILE="$(get_project_meta "$project" package_opencv_profile)"
PACKAGE_DEPENDS="$(get_project_meta "$project" package_depends)"
local serialized
serialized="${PROJECT_COPY_SPECS[$project]:-}"
@ -154,14 +170,25 @@ _finalize_project_meta() {
fi
fi
: "${meta_ref[package_icon_source_rel]:=${meta_ref[app_dir_rel]}/resource/logo.png}"
if [ "${meta_ref[package_kind]}" = "desktop" ]; then
: "${meta_ref[package_icon_dest_rel]:=usr/share/pixmaps/${package_slug}.png}"
local default_icon_rel="${meta_ref[app_dir_rel]}/resource/logo.png"
if [ -z "${meta_ref[package_icon_source_rel]}" ] && [ -f "${SCRIPT_DIR}/../${default_icon_rel}" ]; then
meta_ref[package_icon_source_rel]="${default_icon_rel}"
fi
if [ -n "${meta_ref[package_icon_source_rel]}" ]; then
: "${meta_ref[package_icon_dest_rel]:=usr/share/pixmaps/${package_slug}.png}"
fi
fi
if [ "${meta_ref[package_kind]}" = "service" ]; then
: "${meta_ref[package_service_source_rel]:=${meta_ref[app_dir_rel]}/${package_slug}.service}"
# 优先使用 service/ 子目录下的 service 文件,不存在则回退到 app_dir 根目录
local _svc_in_service_dir="${meta_ref[app_dir_rel]}/service/${package_slug}.service"
local _svc_in_root="${meta_ref[app_dir_rel]}/${package_slug}.service"
if [ -f "${SCRIPT_DIR}/../${_svc_in_service_dir}" ]; then
: "${meta_ref[package_service_source_rel]:=$_svc_in_service_dir}"
else
: "${meta_ref[package_service_source_rel]:=$_svc_in_root}"
fi
: "${meta_ref[package_service_dest_rel]:=opt/${package_slug}/${package_slug}.service}"
fi
fi
@ -196,6 +223,7 @@ _register_project() {
[package_service_dest_rel]=""
[package_ld_conf_extra_lines]=""
[package_opencv_profile]=""
[package_depends]=""
)
local kv key value
@ -269,7 +297,7 @@ _parse_kv_overrides() {
_register_desktop_projects() {
local entry fields name algo
local app_dir version_macro opencv algo_copy device_copy extra_copy optional_copy ld_conf description comment
local app_dir version_macro opencv algo_copy device_copy extra_copy optional_copy ld_conf description comment package_depends
for entry in "${DESKTOP_PROJECTS[@]}"; do
IFS=';' read -ra fields <<< "$entry"
@ -280,12 +308,14 @@ _register_desktop_projects() {
app_dir="App/${name}/${name}App"
version_macro=$(printf '%s' "$name" | tr '[:lower:]' '[:upper:]')
opencv="" algo_copy="" device_copy="$COPY_VZNLSDK" extra_copy="" optional_copy=""
ld_conf="" description="" comment=""
ld_conf="" description="" comment="" package_depends=""
_parse_kv_overrides fields 2 \
app_dir version_macro opencv algo_copy device_copy extra_copy optional_copy ld_conf description comment
app_dir version_macro opencv algo_copy device_copy extra_copy optional_copy ld_conf description comment package_depends
: "${algo_copy:=glob|AppAlgo/${algo}/Arm/aarch64/*.so|usr/lib}"
if [ -z "$algo_copy" ] && [ "$algo" != "none" ]; then
algo_copy="glob|AppAlgo/${algo}/Arm/aarch64/*.so|usr/lib"
fi
# register
local reg_args=()
@ -293,18 +323,22 @@ _register_desktop_projects() {
[ -n "$ld_conf" ] && reg_args+=("package_ld_conf_extra_lines=$ld_conf")
[ -n "$description" ] && reg_args+=("package_description=$description")
[ -n "$comment" ] && reg_args+=("package_comment=$comment")
[ -n "$package_depends" ] && reg_args+=("package_depends=$package_depends")
_register_desktop_project "$name" "$app_dir" "$version_macro" "${reg_args[@]}"
# copy specs
local specs=("$algo_copy")
local specs=()
[ -n "$algo_copy" ] && [ "$algo_copy" != "none" ] && specs+=("$algo_copy")
[ -n "$device_copy" ] && [ "$device_copy" != "none" ] && specs+=("$device_copy")
[ -n "$extra_copy" ] && specs+=("$extra_copy")
_split_csv_specs "$extra_copy" specs
_set_copy_specs "$name" "${specs[@]}"
# optional copy
if [ -n "$optional_copy" ]; then
_set_optional_copy_specs "$name" "$optional_copy"
local opt_specs=()
_split_csv_specs "$optional_copy" opt_specs
if [ ${#opt_specs[@]} -gt 0 ]; then
_set_optional_copy_specs "$name" "${opt_specs[@]}"
elif [ "$device_copy" != "$COPY_VZNLSDK" ]; then
_set_optional_copy_specs "$name" "$COPY_VZNLSDK"
fi
@ -313,7 +347,7 @@ _register_desktop_projects() {
_register_service_projects() {
local entry fields name algo
local app_suffix version_macro opencv device_copy description
local app_dir app_suffix version_macro opencv device_copy extra_copy optional_copy description package_depends
for entry in "${SERVICE_PROJECTS[@]}"; do
IFS=';' read -ra fields <<< "$entry"
@ -321,30 +355,45 @@ _register_service_projects() {
algo="$(_trim_leading_spaces "${fields[1]}")"
# 默认值
app_suffix="Server" version_macro="" opencv=""
device_copy="$COPY_VZNLSDK" description=""
app_dir="" app_suffix="Server" version_macro="" opencv=""
device_copy="$COPY_VZNLSDK" extra_copy="" optional_copy="" description="" package_depends=""
_parse_kv_overrides fields 2 \
app_suffix version_macro opencv device_copy description
app_dir app_suffix version_macro opencv device_copy extra_copy optional_copy description package_depends
: "${app_dir:=App/${name}/${name}${app_suffix}}"
: "${version_macro:=$(printf '%s' "$name" | tr '[:lower:]' '[:upper:]')}"
# register
local reg_args=()
[ -n "$opencv" ] && reg_args+=("package_opencv_profile=$opencv")
[ -n "$description" ] && reg_args+=("package_description=$description")
[ -n "$package_depends" ] && reg_args+=("package_depends=$package_depends")
_register_service_project "$name" "App/${name}/${name}${app_suffix}" "$version_macro" "${reg_args[@]}"
_set_copy_specs "$name" "glob|AppAlgo/${algo}/Arm/aarch64/*.so|usr/lib" "$device_copy"
_register_service_project "$name" "$app_dir" "$version_macro" "${reg_args[@]}"
# copy specs
local specs=()
[ "$algo" != "none" ] && specs+=("glob|AppAlgo/${algo}/Arm/aarch64/*.so|usr/lib")
[ -n "$device_copy" ] && [ "$device_copy" != "none" ] && specs+=("$device_copy")
_split_csv_specs "$extra_copy" specs
_set_copy_specs "$name" "${specs[@]}"
# optional copy
local opt_specs=()
_split_csv_specs "$optional_copy" opt_specs
# 自动检测 config 目录
local app_dir="App/${name}/${name}${app_suffix}"
if [ -d "${SCRIPT_DIR}/../${app_dir}/config" ]; then
local pkg_name
pkg_name=$(get_project_meta "$name" package_name)
local slug
slug=$(printf '%s' "$pkg_name" | tr '[:upper:]' '[:lower:]')
_set_optional_copy_specs "$name" "dir|${app_dir}/config|opt/${slug}/config"
opt_specs+=("dir|${app_dir}/config|opt/${slug}/config")
fi
if [ ${#opt_specs[@]} -gt 0 ]; then
_set_optional_copy_specs "$name" "${opt_specs[@]}"
fi
done
}

View File

@ -16,6 +16,21 @@ QT_LIB_PATH="/opt/sysroot/firefly-arm64-sysroot-20.04/lib/aarch64-linux-gnu"
DEFAULT_OPENCV_PROFILE="opencv320"
OPENCV320_LIB_GLOB="SDK/OpenCV320/Arm/aarch64/libopencv_*.so*"
OPENCV480_LIB_GLOB="SDK/OpenCV480/Arm/aarch64/lib/libopencv_*.so*"
OPENCV450_LIB_DIR_CANDIDATES=(
"AppAlgo/stereo_bolt_delivery/lib/opencv"
"AppAlgo/stereo_bolt_delivery/lib"
"SDK/OpenCV450/Arm/aarch64/lib"
"SDK/OpenCV45D/Arm/aarch64/lib"
"SDK/OpenCV454/Arm/aarch64/lib"
"SDK/OpenCV45/Arm/aarch64/lib"
)
OPENCV450_REQUIRED_LIBS=(
"libopencv_calib3d.so.4.5"
"libopencv_imgcodecs.so.4.5"
"libopencv_dnn.so.4.5"
"libopencv_imgproc.so.4.5"
"libopencv_core.so.4.5"
)
FIXED_PACKAGE_MAINTAINER="visionrobot"
FIXED_QT_REMOVE_WEBENGINE=true
FIXED_QT_REMOVE_NFC=true
@ -195,6 +210,16 @@ process_specs() {
done
}
resolve_package_source_path() {
local src=$1
if [[ "${src}" = /* ]]; then
printf '%s\n' "${src}"
else
printf '%s/%s\n' "${REPO_DIR}" "${src}"
fi
}
apply_package_defaults() {
PACKAGE_SLUG=$(printf '%s' "${PACKAGE_NAME}" | tr '[:upper:]' '[:lower:]')
PACKAGE_SECTION="${PACKAGE_SLUG}app"
@ -258,6 +283,10 @@ copy_opencv_runtime() {
opencv480)
src_pattern="${OPENCV480_LIB_GLOB}"
;;
opencv450|opencv45d)
copy_opencv450_runtime
return 0
;;
none)
return 0
;;
@ -271,6 +300,60 @@ copy_opencv_runtime() {
copy_glob_items "${src_pattern}" "usr/lib" "globa" true
}
copy_opencv450_runtime() {
local candidates=()
local candidate=""
local candidate_abs=""
local selected_dir=""
local lib=""
local missing=false
if [ -n "${DRONESCREW_OPENCV450_LIB_DIR:-}" ]; then
candidates+=("${DRONESCREW_OPENCV450_LIB_DIR}")
fi
candidates+=("${OPENCV450_LIB_DIR_CANDIDATES[@]}")
for candidate in "${candidates[@]}"; do
candidate_abs="$(resolve_package_source_path "${candidate}")"
[ -d "${candidate_abs}" ] || continue
missing=false
for lib in "${OPENCV450_REQUIRED_LIBS[@]}"; do
if [ ! -e "${candidate_abs}/${lib}" ]; then
missing=true
break
fi
done
if [ "${missing}" = false ]; then
selected_dir="${candidate_abs}"
break
fi
done
if [ -z "${selected_dir}" ]; then
{
echo "Missing OpenCV 4.5.0 runtime for ${PACKAGE_NAME}."
echo "libstereo_bolt.so requires these exact sonames:"
printf ' %s\n' "${OPENCV450_REQUIRED_LIBS[@]}"
echo "Put them in AppAlgo/stereo_bolt_delivery/lib/opencv,"
echo "or set DRONESCREW_OPENCV450_LIB_DIR to the directory that contains them."
} >&2
exit 1
fi
log_info "Copy OpenCV runtime (opencv450) from ${selected_dir}"
mkdir -p "${PKG_PATH}/usr/lib"
shopt -s nullglob
local opencv_libs=( "${selected_dir}"/libopencv_*.so.4.5* )
shopt -u nullglob
for lib in "${opencv_libs[@]}"; do
cp -a "${lib}" "${PKG_PATH}/usr/lib/"
done
}
copy_required_files() {
copy_opencv_runtime
process_specs true "${PACKAGE_COPY_SPECS[@]}"
@ -297,6 +380,10 @@ Priority: optional
Maintainer: ${PACKAGE_MAINTAINER}
Description: ${PACKAGE_DESCRIPTION}
EOF
if [ -n "${PACKAGE_DEPENDS:-}" ]; then
printf 'Depends: %s\n' "${PACKAGE_DEPENDS}" >> "${PKG_PATH}/DEBIAN/control"
fi
}
set_permissions() {
@ -366,7 +453,11 @@ load_package_context() {
DEB_FILENAME="${RELEASE_PATH}/${PACKAGE_NAME}_${PKG_VERSION}_${BUILD_NUMBER}_${PKG_ARCH}_${TIMESTAMP}.deb"
DESKTOP_FILE_NAME="${PACKAGE_SLUG}.desktop"
EXEC_INSTALL_PATH="/${PACKAGE_BINARY_DEST_REL}"
ICON_INSTALL_PATH="/${PACKAGE_ICON_DEST_REL}"
if [ -n "${PACKAGE_ICON_DEST_REL}" ]; then
ICON_INSTALL_PATH="/${PACKAGE_ICON_DEST_REL}"
else
ICON_INSTALL_PATH="application-x-executable"
fi
SERVICE_INSTALL_DIR="/opt/${PACKAGE_SLUG}"
echo "加载项目 ${PACKAGE_TARGET} 上下文完成"
@ -401,7 +492,11 @@ finalize_package() {
log_info "设置文件权限"
set_permissions
log_info "生成 DEB 安装包"
fakeroot dpkg -b "${PKG_PATH}" "${DEB_FILENAME}" >/dev/null
# 使用低压缩等级避免 OOM大包 + xz 默认等级 6 会吃几百 MB 内存)
# 可通过 DEB_COMPRESS_OPTS 环境变量覆盖,例如 DEB_COMPRESS_OPTS="-Zgzip" 或 "-Zxz -z9"
local compress_opts="${DEB_COMPRESS_OPTS:--Zxz -z1}"
fakeroot dpkg-deb ${compress_opts} --build "${PKG_PATH}" "${DEB_FILENAME}" >/dev/null
log_info "安装包已生成: ${DEB_FILENAME}"
log_info "安装包大小: $(ls -lh "${DEB_FILENAME}" | awk '{print $5}')"

View File

@ -13,27 +13,114 @@ set -e
SERVICE_SOURCE="${SERVICE_INSTALL_PATH}"
SERVICE_UNIT="${SERVICE_UNIT_NAME}"
SERVICE_TARGET="/etc/systemd/system/\${SERVICE_UNIT}"
LD_CONF_FILE="/etc/ld.so.conf.d/${PACKAGE_SLUG}.conf"
PKG_NAME="${PACKAGE_NAME}"
PKG_VERSION="${PKG_RELEASE_VERSION}"
INSTALL_DIR="${SERVICE_INSTALL_DIR}"
log() { echo "[\${PKG_NAME}] \$*"; }
fail() { echo "[\${PKG_NAME}] [FAIL] \$*" >&2; exit 1; }
log "=========================================="
log "开始安装 \${PKG_NAME} \${PKG_VERSION}"
log " 安装目录: \${INSTALL_DIR}"
log " 服务单元: \${SERVICE_UNIT}"
log "=========================================="
# 1. 配置动态库搜索路径
log "[1/6] 配置动态库搜索路径 \${LD_CONF_FILE}"
cat > "\${LD_CONF_FILE}" <<'LDEOF'
/usr/lib
/opt/sysroot/lib/
LDEOF
ldconfig
log " ldconfig 完成"
if [ ! -f "\${SERVICE_SOURCE}" ]; then
echo "缺少服务文件: \${SERVICE_SOURCE}" >&2
exit 1
# 2. DroneScrewServer 专用:启动 mediamtx
if [ "\${PKG_NAME}" = "DroneScrewServer" ]; then
mkdir -p "\${INSTALL_DIR}/logs"
log "[2/6] 检测并启动 mediamtx RTSP 服务器"
MEDIAMTX_BIN="\${INSTALL_DIR}/bin/mediamtx"
MEDIAMTX_CFG="\${INSTALL_DIR}/etc/mediamtx_dronescrew.yml"
MEDIAMTX_SERVICE="\${INSTALL_DIR}/etc/mediamtx-dronescrew.service"
if [ -f "\${MEDIAMTX_BIN}" ] && [ -f "\${MEDIAMTX_CFG}" ] && [ -f "\${MEDIAMTX_SERVICE}" ]; then
chmod +x "\${MEDIAMTX_BIN}"
# 复制 service 文件到 systemd 目录
cp "\${MEDIAMTX_SERVICE}" /etc/systemd/system/mediamtx-dronescrew.service
chmod 644 /etc/systemd/system/mediamtx-dronescrew.service
systemctl daemon-reload
systemctl enable mediamtx-dronescrew.service >/dev/null 2>&1 || true
systemctl restart mediamtx-dronescrew.service
sleep 1
if systemctl is-active --quiet mediamtx-dronescrew.service; then
log " [OK] mediamtx 已启动 (端口 8554)"
else
log " [WARN] mediamtx 启动失败DroneScrewServer RTSP 推流可能不可用"
log " 手动检查: journalctl -u mediamtx-dronescrew.service -n 20"
fi
else
log " [WARN] mediamtx 文件缺失,跳过启动"
fi
else
log "[2/6] 跳过 mediamtx非 DroneScrewServer"
fi
# 3. 校验服务文件
log "[3/6] 校验服务文件"
[ -f "\${SERVICE_SOURCE}" ] || fail "缺少服务文件: \${SERVICE_SOURCE}"
log " OK: \${SERVICE_SOURCE}"
# 4. 停止/禁用旧服务
log "[4/6] 停止旧服务(如在运行)"
if systemctl is-active --quiet "\${SERVICE_UNIT}" 2>/dev/null; then
systemctl stop "\${SERVICE_UNIT}"
log " 已停止 \${SERVICE_UNIT}"
else
log " 旧服务未运行,跳过"
fi
if systemctl is-enabled --quiet "\${SERVICE_UNIT}" 2>/dev/null; then
systemctl disable "\${SERVICE_UNIT}"
systemctl disable "\${SERVICE_UNIT}" >/dev/null 2>&1 || true
log " 已禁用旧 unit"
fi
# 5. 注册新 unit
log "[5/6] 注册并启用新 unit"
cp "\${SERVICE_SOURCE}" "\${SERVICE_TARGET}"
chmod 644 "\${SERVICE_TARGET}"
systemctl daemon-reload
systemctl enable "\${SERVICE_UNIT}"
systemctl enable "\${SERVICE_UNIT}" >/dev/null 2>&1
log " 已 enable \${SERVICE_UNIT}"
# 6. 启动并验证
log "[6/6] 启动服务"
set +e
systemctl start "\${SERVICE_UNIT}"
START_RC=\$?
set -e
sleep 1
if systemctl is-active --quiet "\${SERVICE_UNIT}"; then
log " [OK] \${SERVICE_UNIT} 已在运行"
systemctl status "\${SERVICE_UNIT}" --no-pager --lines=3 || true
log "=========================================="
log "\${PKG_NAME} 安装成功"
log " 查看实时日志: journalctl -u \${SERVICE_UNIT} -f"
log " 停止服务: systemctl stop \${SERVICE_UNIT}"
if [ "\${PKG_NAME}" = "DroneScrewServer" ]; then
log " mediamtx 日志: journalctl -u mediamtx-dronescrew.service -f"
log " RTSP 拉流地址: rtsp://<板子IP>:8554/live/dronescrew"
fi
log "=========================================="
else
log " [FAIL] \${SERVICE_UNIT} 未能启动 (start rc=\${START_RC})"
log " 最近 20 行日志:"
journalctl -u "\${SERVICE_UNIT}" --no-pager --lines=20 || true
fail "服务启动失败,请检查上面的日志"
fi
EOF
chmod +x "${PKG_PATH}/DEBIAN/postinst"
@ -45,14 +132,50 @@ write_service_postrm() {
set -e
SERVICE_UNIT="${SERVICE_UNIT_NAME}"
LD_CONF_FILE="/etc/ld.so.conf.d/${PACKAGE_SLUG}.conf"
PKG_NAME="${PACKAGE_NAME}"
INSTALL_DIR="${SERVICE_INSTALL_DIR}"
ACTION="\${1:-}"
systemctl stop "\${SERVICE_UNIT}" 2>/dev/null || true
systemctl disable "\${SERVICE_UNIT}" 2>/dev/null || true
log() { echo "[\${PKG_NAME}] \$*"; }
log "=========================================="
log "卸载 \${PKG_NAME} (动作: \${ACTION})"
log "=========================================="
log "[1/4] 停止并禁用 \${SERVICE_UNIT}"
systemctl stop "\${SERVICE_UNIT}" 2>/dev/null && log " 已停止" || log " 未在运行"
systemctl disable "\${SERVICE_UNIT}" >/dev/null 2>&1 && log " 已禁用" || true
rm -f "/etc/systemd/system/\${SERVICE_UNIT}"
systemctl daemon-reload
if [ "\${1:-}" = "remove" ]; then
rm -rf "${SERVICE_INSTALL_DIR}"
# DroneScrewServer 专用:停止 mediamtx
if [ "\${PKG_NAME}" = "DroneScrewServer" ]; then
log "[2/4] 停止并删除 mediamtx 服务"
if systemctl is-active --quiet mediamtx-dronescrew.service 2>/dev/null; then
systemctl stop mediamtx-dronescrew.service
log " 已停止 mediamtx"
fi
systemctl disable mediamtx-dronescrew.service >/dev/null 2>&1 || true
rm -f /etc/systemd/system/mediamtx-dronescrew.service
systemctl daemon-reload
log " mediamtx 服务已移除"
else
log "[2/4] 跳过 mediamtx非 DroneScrewServer"
fi
if [ "\${ACTION}" = "remove" ] || [ "\${ACTION}" = "purge" ]; then
log "[3/4] 清理安装目录 \${INSTALL_DIR}"
rm -rf "\${INSTALL_DIR}"
log "[4/4] 清理 ld.so.conf 并刷新缓存"
rm -f "\${LD_CONF_FILE}"
ldconfig
log "=========================================="
log "\${PKG_NAME} 卸载完成"
log "=========================================="
else
log "[3/4] 跳过目录清理(仅升级阶段,保留安装目录)"
log "[4/4] 跳过 ld.so.conf 清理"
fi
EOF

View File

@ -25,8 +25,12 @@
#
# 可选覆盖 (key=value):
# 通用: app_dir version_macro opencv device_copy(none=不复制) description comment
# package_depends(DEB Depends 字段,逗号分隔)
# 桌面: algo_copy extra_copy optional_copy ld_conf
# 服务: app_suffix(默认 Server)
# 服务: app_dir app_suffix(默认 Server) extra_copy optional_copy
#
# 多条 spec: extra_copy 与 optional_copy 支持以英文逗号分隔多条复制规则
# 例: extra_copy=glob|A/*.so|usr/lib,glob|B/*.so|usr/lib
# =============================================================================
SCRIPT_DIR="${SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
@ -34,51 +38,105 @@ source "${SCRIPT_DIR}/project_meta.sh"
COPY_VZNLSDK="glob|SDK/Device/VzNLSDK/Arm/aarch64/*.so|usr/lib"
# 复制编译生成的动态库
# 说明项目内部库绝大多数是静态库CONFIG += staticlib已在链接阶段
# 打进可执行文件,运行时不需要 .so。实测各目录产出
# Device / Utils / VrNets —— 全为静态库,不产出 .so
# Module —— 含 3 个动态库BinocularMarkReceiver /
# FFMediaStream / WDRemoteReceiver需随包部署
COPY_DEVICE_LIBS="globa|GrabBagPrj/buildarm/Device/*/*.so*|usr/lib"
COPY_MODULE_LIBS="globa|GrabBagPrj/buildarm/Module/*/*.so*|usr/lib"
COPY_UTILS_LIBS="globa|GrabBagPrj/buildarm/Utils/*/*.so*|usr/lib"
COPY_VRNETS_LIBS="globa|GrabBagPrj/buildarm/VrNets/*.so*|usr/lib"
# 合并编译生成的动态库:当前仅 Module 实际产出 .so。
# 这些规则走 requiredextra_copy空匹配会导致打包失败
# 因此只能收集确实存在 .so 的目录。若将来 Device/Utils/VrNets
# 某目录新增动态库CONFIG += shared把对应 COPY_*_LIBS 追加到下方即可。
COPY_ALL_BUILD_LIBS="${COPY_MODULE_LIBS}"
# ===== 桌面项目 =====
DESKTOP_PROJECTS=(
"GrabBag;bagPosition"
"LapWeld;lapWeldDetection"
"Workpiece;workpieceCornerExtraction"
"ParticleSize;particleSizeMeasure"
"ScrewPosition;rodAndBarDetection"
"WorkpieceHole;workpieceHolePositioning"
"StatorPosition;motorStatorPosition"
"RodAndBarPosition;rodAndBarDetection"
"RodWeldSeam;rodAndBarDetection"
"HolePitPosition;workpieceHolePositioning"
"WheelMeasure;wheelArchHeigthMeasure"
"GrabBag;bagPosition;\
extra_copy=${COPY_ALL_BUILD_LIBS}"
"LapWeld;lapWeldDetection;\
extra_copy=${COPY_ALL_BUILD_LIBS}"
"Workpiece;workpieceCornerExtraction;\
extra_copy=${COPY_ALL_BUILD_LIBS}"
"ParticleSize;particleSizeMeasure;\
extra_copy=${COPY_ALL_BUILD_LIBS}"
"ScrewPosition;rodAndBarDetection;\
extra_copy=${COPY_ALL_BUILD_LIBS}"
"WorkpieceHole;workpieceHolePositioning;\
extra_copy=${COPY_ALL_BUILD_LIBS}"
"DiscHolePose;workpieceHolePositioning;\
extra_copy=${COPY_ALL_BUILD_LIBS}"
"TireHolePose;workpieceHolePositioning;\
extra_copy=${COPY_ALL_BUILD_LIBS}"
"StatorPosition;motorStatorPosition;\
extra_copy=${COPY_ALL_BUILD_LIBS}"
"RodAndBarPosition;rodAndBarDetection;\
extra_copy=${COPY_ALL_BUILD_LIBS}"
"RodWeldSeam;rodAndBarDetection;\
extra_copy=${COPY_ALL_BUILD_LIBS}"
"HolePitPosition;workpieceHolePositioning;\
extra_copy=${COPY_ALL_BUILD_LIBS}"
"WheelMeasure;wheelArchHeigthMeasure;\
extra_copy=${COPY_ALL_BUILD_LIBS}"
"BagThreadPosition;bagThreadPositioning;\
extra_copy=glob|SDK/Device/gl_linelaser_sdk/aarch64_linux/*.so|usr/lib"
extra_copy=${COPY_ALL_BUILD_LIBS},glob|SDK/Device/gl_linelaser_sdk/aarch64_linux/*.so|usr/lib"
"HoleDetection;holeDetection;\
algo_copy=globa|AppAlgo/holeDetection/arm/release/*.so*|usr/lib"
algo_copy=globa|AppAlgo/holeDetection/arm/release/*.so*|usr/lib;\
extra_copy=${COPY_ALL_BUILD_LIBS}"
"TunnelChannel;channelSpaceMeasure;\
ld_conf=/usr/lib/HCNetSDKCom;\
extra_copy=globa|SDK/Device/HCNetSDK/Arm64/lib/*.so*|usr/lib;\
extra_copy=${COPY_ALL_BUILD_LIBS},globa|SDK/Device/HCNetSDK/Arm64/lib/*.so*|usr/lib;\
optional_copy=globa|SDK/Device/HCNetSDK/Arm64/lib/HCNetSDKCom/*.so*|usr/lib/HCNetSDKCom"
"WorkpiecePosition;binocularMark;\
app_dir=App/WorkpieceProject/WorkpiecePositionApp;\
version_macro=WORKPIECEPOSITION;\
opencv=opencv480;\
device_copy=globa|SDK/Device/EpicEye/lib/arm/*.so*|usr/lib"
device_copy=globa|SDK/Device/EpicEye/lib/arm/*.so*|usr/lib;\
extra_copy=${COPY_ALL_BUILD_LIBS}"
"WorkpieceSplice;binocularMark;\
app_dir=App/WorkpieceProject/WorkpieceSpliceApp;\
version_macro=WORKPIECESPLICE;\
opencv=opencv480;\
device_copy=none"
device_copy=none;\
extra_copy=${COPY_ALL_BUILD_LIBS}"
"DroneScrewCtrlApp;none;\
app_dir=App/DroneScrewbolt/DroneScrewCtrlApp;\
version_macro=DRONESCREWCTRL;\
device_copy=none;\
extra_copy=${COPY_ALL_BUILD_LIBS},globa|SDK/ZeroMQ/Arm/aarch64/libzmq.so*|usr/lib;\
description=DroneScrew Ctrl (board 2)"
)
# ===== 服务项目 =====
SERVICE_PROJECTS=(
"BeltTearing;beltTearing"
"BeltTearing;beltTearing;\
extra_copy=${COPY_ALL_BUILD_LIBS}"
"BinocularMark;binocularMark;\
opencv=opencv480;\
device_copy=glob|SDK/Device/Galaxy/aarch64/lib/armv8/*|usr/lib"
device_copy=glob|SDK/Device/Galaxy/aarch64/lib/armv8/*|usr/lib;\
extra_copy=${COPY_ALL_BUILD_LIBS}"
"DroneScrewServer;none;\
app_dir=App/DroneScrewbolt/DroneScrewServer;\
version_macro=DRONESCREWSERVER;\
app_suffix=Server;\
opencv=opencv450;\
device_copy=globa|SDK/Device/MVS/MVS_STD_V3.0.1_251113/MVS-3.0.1_aarch64_20251113/lib/aarch64/*.so*|usr/lib;\
extra_copy=${COPY_ALL_BUILD_LIBS},globa|SDK/ZeroMQ/Arm/aarch64/libzmq.so*|usr/lib,file|SDK/mediamtx/arm64/mediamtx|opt/dronescrewserver/bin/mediamtx,file|App/DroneScrewbolt/DroneScrewServer/service/mediamtx_dronescrew.yml|opt/dronescrewserver/etc/mediamtx_dronescrew.yml,file|App/DroneScrewbolt/DroneScrewServer/service/mediamtx-dronescrew.service|opt/dronescrewserver/etc/mediamtx-dronescrew.service,globa|AppAlgo/stereo_bolt_delivery/lib/*.so*|usr/lib,dir|AppAlgo/stereo_bolt_delivery/config|opt/dronescrewserver/config,dir|AppAlgo/stereo_bolt_delivery/calib|opt/dronescrewserver/calib,dir|AppAlgo/stereo_bolt_delivery/weights|opt/dronescrewserver/weights;\
optional_copy=globa|SDK/Protobuf_3_20/Arm/aarch64/libprotobuf.so*|usr/lib,globa|SDK/Device/MVS/MVS_STD_V3.0.1_251113/MVS-3.0.1_aarch64_20251113/lib/aarch64/ThirdParty/*.so*|usr/lib;\
description=DroneScrew Server (board 1)"
)
# ===== 执行注册 =====

View File

@ -58,8 +58,27 @@ echo "版本文件: ${VERSION_FILE}"
echo "完整版本号: ${FULL_VERSION}"
if ! grep -qE "\\|[[:space:]]*${PUBLISH_NAME}[[:space:]]*\\|" "${APP_LIST_FILE}"; then
echo "错误: AppList.md 中未找到 ${PUBLISH_NAME}" >&2
exit 1
echo "警告: AppList.md 中未找到 ${PUBLISH_NAME},自动添加..."
# 获取当前最大序号
max_num=$(awk -F'|' 'NF >= 6 && $2 ~ /^[[:space:]]*[0-9]+[[:space:]]*$/ {
num = $2
gsub(/^[[:space:]]+|[[:space:]]+$/, "", num)
if (num > max) max = num
} END { print max+0 }' "${APP_LIST_FILE}")
next_num=$((max_num + 1))
# 获取项目描述(如果有)
project_desc=$(get_project_meta "${VERSION_PROJECT}" package_description)
if [ -z "$project_desc" ]; then
project_desc="${VERSION_PROJECT}"
fi
# 在表格末尾(最后一个空行之前)添加新行
sed -i "/^$/i | ${next_num} | ${project_desc} | ${PUBLISH_NAME} | ${FULL_VERSION} |" "${APP_LIST_FILE}"
echo "已添加 ${PUBLISH_NAME} 到 AppList.md (序号: ${next_num})"
fi
tmp_file="$(mktemp)"

View File

@ -5,6 +5,53 @@
#include <Eigen/SVD>
#include <cmath>
namespace
{
double NormalizeAnglePi(double angle)
{
double normalized = std::fmod(angle + M_PI, 2.0 * M_PI);
if (normalized < 0.0) {
normalized += 2.0 * M_PI;
}
return normalized - M_PI;
}
void GetEulerOrderAxes(HECEulerOrder order, int axes[3])
{
switch (order) {
case HECEulerOrder::XYZ: axes[0] = 0; axes[1] = 1; axes[2] = 2; break;
case HECEulerOrder::XZY: axes[0] = 0; axes[1] = 2; axes[2] = 1; break;
case HECEulerOrder::YXZ: axes[0] = 1; axes[1] = 0; axes[2] = 2; break;
case HECEulerOrder::YZX: axes[0] = 1; axes[1] = 2; axes[2] = 0; break;
case HECEulerOrder::ZXY: axes[0] = 2; axes[1] = 0; axes[2] = 1; break;
case HECEulerOrder::ZYX: axes[0] = 2; axes[1] = 1; axes[2] = 0; break;
}
}
void NormalizeEulerBranch(HECEulerOrder order, HECEulerAngles& angles)
{
double values[3] = { angles.roll, angles.pitch, angles.yaw };
int axes[3] = { 0, 1, 2 };
GetEulerOrderAxes(order, axes);
if (std::abs(values[axes[1]]) > M_PI / 2.0) {
values[axes[0]] = NormalizeAnglePi(values[axes[0]] + M_PI);
values[axes[1]] = NormalizeAnglePi(M_PI - values[axes[1]]);
values[axes[2]] = NormalizeAnglePi(values[axes[2]] + M_PI);
} else {
values[0] = NormalizeAnglePi(values[0]);
values[1] = NormalizeAnglePi(values[1]);
values[2] = NormalizeAnglePi(values[2]);
}
angles.roll = values[0];
angles.pitch = values[1];
angles.yaw = values[2];
}
} // namespace
HandEyeCalib::HandEyeCalib()
{
}
@ -195,6 +242,8 @@ void HandEyeCalib::RotationMatrixToEuler(
angles.roll = ea[0]; angles.pitch = ea[1]; angles.yaw = ea[2];
break;
}
NormalizeEulerBranch(order, angles);
}
void HandEyeCalib::EulerToRotationMatrix(

View File

@ -0,0 +1,118 @@
#ifndef IWDREMOTERECEIVER_H
#define IWDREMOTERECEIVER_H
#include "WDRemoteDataTypes.h"
#include <functional>
#include <string>
#include <vector>
// 导出宏定义
#ifdef _WIN32
# ifdef WDREMOTERECEIVER_LIBRARY
# define WDREMOTERECEIVER_EXPORT __declspec(dllexport)
# else
# define WDREMOTERECEIVER_EXPORT __declspec(dllimport)
# endif
#else
# define WDREMOTERECEIVER_EXPORT __attribute__((visibility("default")))
#endif
/**
* @brief
*
* ZMQ + + 访
*
*
* 使
* - IVrZeroMQClient ( REQ-REP)
* - IVrZeroMQSubscriber ( PUB-SUBtopic="result")
* - IVrZeroMQSubscriber ( PUB-SUBtopic="raw")
*
*
* IWDRemoteReceiver* p = nullptr;
* IWDRemoteReceiver::CreateInstance(&p);
* p->SetEventCallback(...);
* p->SetDetectionCallback(...);
* p->SetRawImageCallback(...); // 双目图像
* std::vector<WDRemoteDeviceInfo> devices;
* p->SearchDevices(devices); // UDP 广播发现设备
* p->Open(devices[0].machineCode); // 按机器码打开指定设备
* p->StartWork(WDRemoteWorkMode::Detection);
* ...
* p->StopWork(); // 发送停止指令
* p->Disconnect(); // 断开所有通道
*/
class WDREMOTERECEIVER_EXPORT IWDRemoteReceiver
{
public:
virtual ~IWDRemoteReceiver() = default;
/**
* @brief
*/
struct ConnectConfig
{
std::string serverIp{"127.0.0.1"};
int controlPort{5555}; // ZMQ REQ-REP 控制
int resultPort{5556}; // ZMQ PUB 检测结果
int rawImagePort{5557}; // ZMQ PUB 原始图像0 表示不启用
};
// 回调类型
using DetectionCallback = std::function<void(const WDRemoteDetectionFrame& frame)>;
using RawImageCallback = std::function<void(const WDRemoteBinocularRawImage& image)>;
using EventCallback = std::function<void(WDRemoteEventType ev,
const std::string& msg)>;
// 连接 / 断开。推荐使用 SearchDevices + OpenConnect 保留给兼容旧调用。
virtual int Connect(const ConnectConfig& cfg) = 0;
virtual int SearchDevices(std::vector<WDRemoteDeviceInfo>& devices,
int timeoutMs = 150) = 0;
virtual int Open(const std::string& machineCode,
int timeoutMs = 3000) = 0;
virtual int Disconnect() = 0;
virtual bool IsConnected() const = 0;
// 控制接口(内部同步走 ZMQ REQ-REP
virtual int StartWork(int timeoutMs = 3000) = 0;
virtual int StartWork(WDRemoteWorkMode mode, int timeoutMs = 3000) = 0;
virtual int StopWork (int timeoutMs = 3000) = 0;
virtual int StopWork (WDRemoteWorkMode mode, int timeoutMs = 3000) = 0;
virtual int RequestSingleDetection(int timeoutMs = 15000) = 0;
virtual int SetExposure(double exposureTime, int timeoutMs = 3000) = 0;
virtual int SetGain (double gain, int timeoutMs = 3000) = 0;
virtual int SetAlgoParams(const WDRemoteAlgoParams& params, int timeoutMs = 3000) = 0;
virtual WDRemoteServerInfo GetServerInfo(int timeoutMs = 3000) = 0;
/**
* @brief
* @param mode "binocular" "mono"
*
* StartWork(Detection)
*
*/
virtual void SetDetectMode(const std::string& mode) = 0;
/**
* @brief JSON ZMQ REP socket
*
* "unknown cmd"
*
*/
virtual int SendCustomCommand(const std::string& jsonReq,
std::string& jsonResp,
int timeoutMs = 3000) = 0;
// 回调注入
virtual void SetDetectionCallback(DetectionCallback cb) = 0;
virtual void SetRawImageCallback (RawImageCallback cb) = 0; // 双目图像
virtual void SetEventCallback (EventCallback cb) = 0;
/**
* @brief
*/
static int CreateInstance(IWDRemoteReceiver** ppReceiver);
};
#endif // IWDREMOTERECEIVER_H

View File

@ -0,0 +1,357 @@
#ifndef WDREMOTEDATATYPES_H
#define WDREMOTEDATATYPES_H
#include <functional>
#include <vector>
#include <string>
#include <cstdint>
#include <cstddef>
/**
* @file WDRemoteDataTypes.h
* @brief WDRemoteReceiver
*
* WDRemoteReceiver 使
*/
/**
* @brief
*
* ZMQ PUB pixelFormat
*/
enum class WDRemotePixelFormat : uint32_t
{
UNKNOWN = 0xFFFFFFFFu,
MONO8 = 0,
BGR8 = 1,
RGB8 = 2,
BAYERRG8 = 3,
NV12 = 4,
};
/**
* @brief
*/
enum class WDRemoteEventType
{
UNKNOWN,
CONNECTED, // 控制通道已建立
DISCONNECTED, // 控制通道已断开
CONNECTION_ERROR, // 连接错误
REQUEST_TIMEOUT, // 同步请求超时
};
/**
* @brief
*
* DroneScrewBox 使
*
*/
struct WDRemoteDetectionBox
{
int classId{0};
float score{0.0f};
int x{0};
int y{0};
int width{0};
int height{0};
};
/**
* @brief
*/
// Stereo distance result in millimeters.
struct WDRemoteDetectionDistance
{
int fromId{0};
int toId{0};
float distanceMm{0.0f};
};
struct WDRemoteDetectionFrame
{
uint64_t frameId{0};
int64_t timestampUs{0};
int imageWidth{0};
int imageHeight{0};
bool success{true};
int errorCode{0};
std::string message;
std::vector<WDRemoteDetectionBox> boxes;
std::vector<WDRemoteDetectionDistance> distances;
};
/**
* @brief
*/
struct WDRemoteAlgoParams
{
float scoreThreshold{0.5f};
float nmsThreshold{0.45f};
int inputWidth{640};
int inputHeight{640};
std::string modelPath;
int modelType{0}; // 0=YOLO, 1=自定义
};
struct WDRemoteMatrix
{
int rows{0};
int cols{0};
std::vector<double> data;
bool empty() const { return rows <= 0 || cols <= 0 || data.empty(); }
};
struct WDRemoteCameraCalibration
{
bool valid{false};
int imageWidth{0};
int imageHeight{0};
double rms{0.0};
WDRemoteMatrix cameraMatrix;
WDRemoteMatrix distCoeffs;
};
struct WDRemoteStereoCalibration
{
bool valid{false};
double rms{0.0};
double baselineMm{0.0};
WDRemoteMatrix R;
WDRemoteMatrix T;
WDRemoteMatrix E;
WDRemoteMatrix F;
WDRemoteMatrix R1;
WDRemoteMatrix R2;
WDRemoteMatrix P1;
WDRemoteMatrix P2;
WDRemoteMatrix Q;
};
struct WDRemoteCalibrationInfo
{
bool valid{false};
std::string sourcePath;
std::string algoConfigPath;
std::string message;
WDRemoteCameraCalibration left;
WDRemoteCameraCalibration right;
WDRemoteStereoCalibration stereo;
};
/**
* @brief GetServerInfo
*/
struct WDRemoteServerInfo
{
bool ok{false};
std::string rtspUrl;
std::string version;
int controlPort{0};
int resultPort{0};
int rawImagePort{0};
double detectFps{0.0};
double streamFps{0.0};
double leftExposure{0.0};
double rightExposure{0.0};
double leftGain{0.0};
double rightGain{0.0};
int leftCameraIndex{0};
int rightCameraIndex{1};
std::string leftCameraSerial;
std::string rightCameraSerial;
int streamWidth{0};
int streamHeight{0};
std::string streamCamera;
int binningHorizontal{0};
int binningVertical{0};
std::string mode;
std::string detectMode;
// 算法参数
float algoScore{0.5f};
float algoNms{0.45f};
int algoWidth{640};
int algoHeight{640};
std::string algoModel;
int algoModelType{0};
WDRemoteCalibrationInfo calibration;
std::string rawJson; // 原始 JSON 应答,方便扩展字段
};
/**
* @brief SearchDevices
*/
struct WDRemoteDeviceInfo
{
std::string machineCode;
std::string clientMachineCode;
std::string serverIp;
std::string deviceName;
int controlPort{5555};
int resultPort{5556};
int rawImagePort{5557};
std::string rtspUrl;
double detectFps{0.0};
double streamFps{0.0};
double leftExposure{0.0};
double rightExposure{0.0};
double leftGain{0.0};
double rightGain{0.0};
int leftCameraIndex{0};
int rightCameraIndex{1};
std::string leftCameraSerial;
std::string rightCameraSerial;
int streamWidth{0};
int streamHeight{0};
std::string streamCamera;
int binningHorizontal{0};
int binningVertical{0};
std::string mode;
std::string detectMode;
std::string rawJson;
};
constexpr uint16_t WD_REMOTE_DISCOVERY_PORT = 5554;
/**
* @brief StartWork
*/
enum class WDRemoteWorkMode
{
LiveStream = 0, // 实时左目传图
Detection = 1, // 检测模式,订阅检测结果和 raw 图像
};
/**
* @brief
*
* - data / dataSize
* -
*/
struct WDRemoteRawImage
{
uint64_t frameId{0};
int64_t timestampUs{0};
int width{0};
int height{0};
int stride{0};
WDRemotePixelFormat pixelFormat{WDRemotePixelFormat::UNKNOWN};
const uint8_t* data{nullptr};
size_t dataSize{0};
};
/**
* @brief
*
*
* - leftData / rightData / dataSize
* -
*/
struct WDRemoteBinocularRawImage
{
uint64_t frameId{0};
int64_t timestampUs{0};
// 左目图像
int leftWidth{0};
int leftHeight{0};
int leftStride{0};
WDRemotePixelFormat leftPixelFormat{WDRemotePixelFormat::UNKNOWN};
const uint8_t* leftData{nullptr};
size_t leftDataSize{0};
// 右目图像
int rightWidth{0};
int rightHeight{0};
int rightStride{0};
WDRemotePixelFormat rightPixelFormat{WDRemotePixelFormat::UNKNOWN};
const uint8_t* rightData{nullptr};
size_t rightDataSize{0};
};
/**
* @brief ZMQ PUB 48
*
* PUB = topic( ZMQ ) + payload
* payload = WDRemoteRawImageHeader (48B) + raw bytes
*
* /便
*/
#pragma pack(push, 1)
struct WDRemoteRawImageHeader
{
uint32_t magic; // 0x52415749 ('RAWI')
uint32_t version; // 1
uint64_t frameId;
int64_t timestampUs;
uint32_t width;
uint32_t height;
uint32_t stride;
uint32_t pixelFormat; // WDRemotePixelFormat
uint32_t dataSize;
uint32_t reserved;
};
#pragma pack(pop)
static_assert(sizeof(WDRemoteRawImageHeader) == 48,
"WDRemoteRawImageHeader must be 48 bytes");
constexpr uint32_t WD_REMOTE_RAW_IMAGE_MAGIC = 0x52415749u; // 'RAWI'
constexpr uint32_t WD_REMOTE_RAW_IMAGE_VERSION = 1u;
/**
* @brief ZMQ PUB 88
*
* PUB = topic( ZMQ ) + payload
* payload = WDRemoteBinocularRawImageHeader (88B) + left raw bytes + right raw bytes
*/
#pragma pack(push, 1)
struct WDRemoteBinocularRawImageHeader
{
uint32_t magic; // 0x42495749 ('BIWI' - Binocular Image) 4B
uint32_t version; // 1 4B
uint64_t frameId; // 8B
int64_t timestampUs; // 8B
// 左目图像信息 (20B)
uint32_t leftWidth;
uint32_t leftHeight;
uint32_t leftStride;
uint32_t leftPixelFormat;
uint32_t leftDataSize;
// 右目图像信息 (20B)
uint32_t rightWidth;
uint32_t rightHeight;
uint32_t rightStride;
uint32_t rightPixelFormat;
uint32_t rightDataSize;
uint32_t reserved[6]; // 24B padding (total: 4+4+8+8+20+20+24=88)
};
#pragma pack(pop)
static_assert(sizeof(WDRemoteBinocularRawImageHeader) == 88,
"WDRemoteBinocularRawImageHeader must be 88 bytes");
constexpr uint32_t WD_REMOTE_BINOCULAR_RAW_IMAGE_MAGIC = 0x42495749u; // 'BIWI'
constexpr uint32_t WD_REMOTE_BINOCULAR_RAW_IMAGE_VERSION = 1u;
/**
* @brief payload
*
* WDRemoteBinocularRawImageHeader::reserved[0]
* - 0: payload left/right
* - 1: payload left/right PNG
* - 2: payload left/right MJPEG/JPEG MPP
* - 3: payload left/right zlib/deflate ·bit-exact
*
* reserved[1] / reserved[2]
*/
constexpr uint32_t WD_REMOTE_IMAGE_COMPRESSION_RAW = 0u;
constexpr uint32_t WD_REMOTE_IMAGE_COMPRESSION_PNG = 1u;
constexpr uint32_t WD_REMOTE_IMAGE_COMPRESSION_MJPEG = 2u;
constexpr uint32_t WD_REMOTE_IMAGE_COMPRESSION_ZLIB = 3u;
#endif // WDREMOTEDATATYPES_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,104 @@
# WDRemoteReceiver.pro
# 远程设备控制 + 结果订阅 + 原始图像订阅 封装库
# 对外提供 IWDRemoteReceiver 接口;下游项目可直接复用,不与 DroneScrew 业务绑定
QT += core network
QT -= gui
TARGET = WDRemoteReceiver
TEMPLATE = lib
CONFIG += c++17
# 全平台使用动态库
CONFIG += shared
# 宏定义
DEFINES += QT_DEPRECATED_WARNINGS
DEFINES += WDREMOTERECEIVER_LIBRARY
# 让本库内部能使用 IVrZeroMQClient / IVrZeroMQSubscriber 的导入宏
win32 {
DEFINES += VR_MQCLIENT_EXPORTS=0
DEFINES += VR_MQPUBSUB_EXPORTS=0
}
unix {
DEFINES += VR_MQCLIENT_EXPORTS=0
DEFINES += VR_MQPUBSUB_EXPORTS=0
}
# UTF-8 编码
win32-msvc {
QMAKE_CXXFLAGS += /utf-8
QMAKE_CFLAGS += /utf-8
}
# 头文件路径
INCLUDEPATH += $$PWD/Inc
INCLUDEPATH += $$PWD/_Inc
INCLUDEPATH += $$PWD/../../Utils/VrUtils/Inc
INCLUDEPATH += $$PWD/../../Utils/VrUtils/jsoncpp
INCLUDEPATH += $$PWD/../../Utils/VrCommon/Inc
INCLUDEPATH += $$PWD/../AuthModule/Inc
# 依赖模块ZeroMQ 控制 + 结果订阅 + 原始图像订阅)
INCLUDEPATH += $$PWD/../ZeroMQClient/Inc
INCLUDEPATH += $$PWD/../ZeroMQPubSub/Inc
# SDK ZeroMQ
INCLUDEPATH += $$PWD/../../SDK/ZeroMQ/include
INCLUDEPATH += $$PWD/../../SDK/OpenCV320/include
HEADERS += \
Inc/IWDRemoteReceiver.h \
_Inc/WDRemoteReceiver.h
SOURCES += \
Src/WDRemoteReceiver.cpp
# 库链接
win32 {
CONFIG(debug, debug|release) {
LIBS += -L../ZeroMQClient/debug -lZeroMQClient
LIBS += -L../ZeroMQPubSub/debug -lZeroMQPubSub
LIBS += -L../AuthModule/debug -lAuthModule
LIBS += -L../../Utils/VrUtils/debug -lVrUtils
} else {
LIBS += -L../ZeroMQClient/release -lZeroMQClient
LIBS += -L../ZeroMQPubSub/release -lZeroMQPubSub
LIBS += -L../AuthModule/release -lAuthModule
LIBS += -L../../Utils/VrUtils/release -lVrUtils
}
# ZeroMQ SDK
CONFIG(debug, debug|release) {
LIBS += -L$$PWD/../../SDK/ZeroMQ/Windows/x64/Debug -llibzmq-v142-mt-gd-4_3_5
} else {
LIBS += -L$$PWD/../../SDK/ZeroMQ/Windows/x64/Release -llibzmq-v142-mt-4_3_5
}
LIBS += -lws2_32
CONFIG(debug, debug|release) {
LIBS += -L$$PWD/../../SDK/OpenCV320/Windows/vc14/Release -lopencv_world320d
} else {
LIBS += -L$$PWD/../../SDK/OpenCV320/Windows/vc14/Release -lopencv_world320
}
}
unix:!macx {
LIBS += -L../ZeroMQClient -lZeroMQClient
LIBS += -L../ZeroMQPubSub -lZeroMQPubSub
LIBS += -L../AuthModule -lAuthModule
LIBS += -L../../Utils/VrUtils -lVrUtils
LIBS += -L$$PWD/../../SDK/ZeroMQ/Arm/aarch64 -lzmq
LIBS += -L$$PWD/../../SDK/OpenCV320/Arm/aarch64 -lopencv_core -lopencv_imgcodecs
LIBS += -lpthread
QMAKE_CXXFLAGS += -fPIC
target.path = /usr/lib
}
!isEmpty(target.path): INSTALLS += target

View File

@ -0,0 +1,96 @@
#ifndef WDREMOTERECEIVER_H
#define WDREMOTERECEIVER_H
#include "IWDRemoteReceiver.h"
#include <atomic>
#include <mutex>
#include <string>
#include <vector>
class IVrZeroMQClient;
class IVrZeroMQSubscriber;
/**
* @brief IWDRemoteReceiver
*
* - IVrZeroMQClient (REQ-REP)
* - IVrZeroMQSubscriber on topic="result"
* - IVrZeroMQSubscriber on topic="raw"
*/
class WDRemoteReceiver : public IWDRemoteReceiver
{
public:
WDRemoteReceiver();
~WDRemoteReceiver() override;
// IWDRemoteReceiver
int Connect(const ConnectConfig& cfg) override;
int SearchDevices(std::vector<WDRemoteDeviceInfo>& devices,
int timeoutMs) override;
int Open(const std::string& machineCode,
int timeoutMs) override;
int Disconnect() override;
bool IsConnected() const override;
int StartWork(int timeoutMs) override;
int StartWork(WDRemoteWorkMode mode, int timeoutMs) override;
int StopWork (int timeoutMs) override;
int StopWork (WDRemoteWorkMode mode, int timeoutMs) override;
int RequestSingleDetection(int timeoutMs) override;
int SetExposure(double exposureTime, int timeoutMs) override;
int SetGain (double gain, int timeoutMs) override;
int SetAlgoParams(const WDRemoteAlgoParams& params, int timeoutMs) override;
WDRemoteServerInfo GetServerInfo(int timeoutMs) override;
int SendCustomCommand(const std::string& jsonReq,
std::string& jsonResp,
int timeoutMs) override;
void SetDetectMode(const std::string& mode) override;
void SetDetectionCallback(DetectionCallback cb) override;
void SetRawImageCallback (RawImageCallback cb) override; // 双目图像
void SetEventCallback (EventCallback cb) override;
private:
int sendCommandAndParse(const std::string& jsonReq,
std::string& jsonResp,
int timeoutMs);
// 内部订阅管理StartWork/StopWork 自动调用)
int StartSubscribeDetection();
int StopSubscribeDetection();
int StartSubscribeRawImage();
int StopSubscribeRawImage();
void onResultMessage (const char* topic, size_t topicLen,
const char* data, size_t dataLen);
void onRawImageMessage(const char* topic, size_t topicLen,
const char* data, size_t dataLen);
void emitEvent(WDRemoteEventType ev, const std::string& msg);
static bool parseDetectionJson(const std::string& jsonStr,
WDRemoteDetectionFrame& outFrame);
static bool parseDeviceInfoJson(const std::string& jsonStr,
const std::string& serverIp,
WDRemoteDeviceInfo& outDevice);
private:
ConnectConfig m_cfg;
std::atomic<bool> m_bConnected{false};
IVrZeroMQClient* m_pCtrlClient{nullptr};
IVrZeroMQSubscriber* m_pSubResult{nullptr};
IVrZeroMQSubscriber* m_pSubRaw{nullptr};
std::mutex m_mtxCtrl; // 串行化 REQ-REP
mutable std::mutex m_mtxCallback;
mutable std::mutex m_mtxDetectMode;
DetectionCallback m_cbDetection;
RawImageCallback m_cbRawImage; // 双目图像回调
EventCallback m_cbEvent;
std::vector<WDRemoteDeviceInfo> m_lastDevices;
std::string m_detectMode{"binocular"}; // "binocular" | "mono"
};
#endif // WDREMOTERECEIVER_H

View File

@ -175,7 +175,7 @@ void CVrZeroMQServer::_WorkTask(zmq::socket_t *socket)
std::string strResponse = m_funRecvAck(static_cast<char*>(msg.data()), msg.size());
zmq::message_t response(strResponse.c_str(), strResponse.size());
socket->send(response, zmq::send_flags::none);
LOG_DEBUG("[ZMQ-REP] _WorkTask send resp: %s\n", strResponse.c_str());
LOG_DEBUG("[ZMQ-REP] _WorkTask send resp bytes=%zu\n", strResponse.size());
} else {
socket->send(msg, zmq::send_flags::none);
}