diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af6438c..77708d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,32 +9,67 @@ on: env: BUILD_TYPE: Release + DEBIAN_FRONTEND: noninteractive + TZ: Asia/Shanghai jobs: build-and-test: - name: Build and Test - ${{ matrix.os }} (${{ matrix.arch }}) + name: Build and Test - ${{ matrix.container || matrix.os }} (${{ matrix.arch }}, ${{ matrix.cmake_build_type }}) runs-on: ${{ matrix.os }} + container: ${{ matrix.container }} strategy: fail-fast: false matrix: include: - # Linux x86_64 (主要测试平台) + # Linux x86_64 (主要测试平台, runner 原生环境) - os: ubuntu-latest + container: '' arch: x86_64 cmake_build_type: Release use_qemu: false # Linux x86_64 Debug 构建 - os: ubuntu-latest + container: '' arch: x86_64 cmake_build_type: Debug use_qemu: false # Linux aarch64 Release 构建 (使用 QEMU 模拟) - os: ubuntu-latest + container: '' arch: aarch64 cmake_build_type: Release use_qemu: true + # Ubuntu 22.04 容器 (Docker) + - os: ubuntu-latest + container: 'ubuntu:22.04' + arch: x86_64 + cmake_build_type: Release + use_qemu: false + # Ubuntu 20.04 容器 (Docker) + - os: ubuntu-latest + container: 'ubuntu:20.04' + arch: x86_64 + cmake_build_type: Release + use_qemu: false steps: + - name: 准备容器环境 (Ubuntu 20.04/22.04) + if: matrix.container != '' + shell: bash + run: | + apt-get update + apt-get install -y --no-install-recommends \ + sudo \ + ca-certificates \ + tzdata \ + git \ + build-essential \ + cmake \ + make \ + libpthread-stubs0-dev \ + tar \ + gzip + - name: 检出代码 uses: actions/checkout@v4 @@ -50,9 +85,9 @@ jobs: path: | build ~/.cmake - key: ${{ runner.os }}-cmake-${{ matrix.arch }}-${{ matrix.cmake_build_type }}-${{ hashFiles('**/CMakeLists.txt') }} + key: ${{ runner.os }}-${{ matrix.container || 'native' }}-cmake-${{ matrix.arch }}-${{ matrix.cmake_build_type }}-${{ hashFiles('**/CMakeLists.txt') }} restore-keys: | - ${{ runner.os }}-cmake-${{ matrix.arch }}-${{ matrix.cmake_build_type }}- + ${{ runner.os }}-${{ matrix.container || 'native' }}-cmake-${{ matrix.arch }}-${{ matrix.cmake_build_type }}- - name: 安装构建依赖 run: | diff --git a/cmake/linkerhand-cpp-sdk-config.cmake b/cmake/linkerhand-cpp-sdk-config.cmake index 792cecf..cbecbb6 100644 --- a/cmake/linkerhand-cpp-sdk-config.cmake +++ b/cmake/linkerhand-cpp-sdk-config.cmake @@ -123,19 +123,31 @@ if(NOT TARGET LinkerHand::linkerhand_cpp_sdk) set_target_properties(LinkerHand::linkerhand_cpp_sdk PROPERTIES IMPORTED_IMPLIB "${_lh_imported_implib}") endif() - # 公共头 CanFD.h / PCANBus.h / CanFrame.h 引用了 third_party 头(Hcanbus.h / - # PCANBasic.h)。把对应 include 目录补到 imported target,下游 link 后无需手动配。 + # 是否启用 CAN-FD:release-package 由 CI 在 USE_CANFD=ON 下打包,默认 ON; + # 下游可显式 -DLINKERHAND_USE_CANFD=OFF 覆盖,跳过 libcanbus 头依赖。 + if(NOT DEFINED LINKERHAND_USE_CANFD) + if(EXISTS "${_lh_thirdparty_root}/libcanbus") + set(LINKERHAND_USE_CANFD ON) + else() + set(LINKERHAND_USE_CANFD OFF) + endif() + endif() + set_property(TARGET LinkerHand::linkerhand_cpp_sdk APPEND PROPERTY + INTERFACE_COMPILE_DEFINITIONS "LINKERHAND_USE_CANFD=$,1,0>") + # 公共头 PCANBus.h / CanFrame.h(以及 CANFD 开启时的 CanFD.h)引用了 third_party + # 头(PCANBasic.h / Hcanbus.h)。把对应 include 目录补到 imported target,下游 link + # 后无需手动配。 if(_lh_thirdparty_root) if(WIN32) - if(EXISTS "${_lh_thirdparty_root}/libcanbus/include/windows") - set_property(TARGET LinkerHand::linkerhand_cpp_sdk APPEND PROPERTY - INTERFACE_INCLUDE_DIRECTORIES "${_lh_thirdparty_root}/libcanbus/include/windows") - endif() if(EXISTS "${_lh_thirdparty_root}/PCAN_Basic/Include") set_property(TARGET LinkerHand::linkerhand_cpp_sdk APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${_lh_thirdparty_root}/PCAN_Basic/Include") endif() - elseif(UNIX AND EXISTS "${_lh_thirdparty_root}/libcanbus/include/linux") + if(LINKERHAND_USE_CANFD AND EXISTS "${_lh_thirdparty_root}/libcanbus/include/windows") + set_property(TARGET LinkerHand::linkerhand_cpp_sdk APPEND PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${_lh_thirdparty_root}/libcanbus/include/windows") + endif() + elseif(UNIX AND LINKERHAND_USE_CANFD AND EXISTS "${_lh_thirdparty_root}/libcanbus/include/linux") set_property(TARGET LinkerHand::linkerhand_cpp_sdk APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${_lh_thirdparty_root}/libcanbus/include/linux") endif() diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 5ea3403..c820bfd 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -97,32 +97,147 @@ include_directories( ${LINKERHAND_INCLUDE_DIR}/core ) +# CAN-FD 总开关: +# - 嵌套模式:父 CMake 已设 USE_CANFD,直接沿用 +# - 独立模式:优先跟随 find_package 拿到的 LINKERHAND_USE_CANFD(release-package +# 据 third_party/libcanbus 是否存在自动推断;安装版 Config 据 SDK 构建时的值固化); +# 都没有再回落到 ON。用户可显式 -DUSE_CANFD=ON/OFF 覆盖。 +if(NOT DEFINED USE_CANFD) + if(DEFINED LINKERHAND_USE_CANFD) + set(USE_CANFD ${LINKERHAND_USE_CANFD} CACHE BOOL "Enable CAN-FD examples and libcanbus runtime deps") + else() + option(USE_CANFD "Enable CAN-FD examples and libcanbus runtime deps" ON) + endif() +endif() + +# 自治降级:USE_CANFD=ON 但本地缺料时自动降级 OFF + 警告,避免突然 Hcanbus.h not found +# 或链接 undefined reference to CanFD::*。两个判据: +# (1) libcanbus 头实际不在场(旧 release 包没带、或被裁过) +# (2) 嵌套以外模式下 SDK .so 不含 CanFD 符号(往往是发布以 USE_CANFD=OFF 构建, +# 通过 third_party/libcanbus 是否随包发布间接判断) +if(USE_CANFD) + if(WIN32) + set(_LH_CANFD_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/libcanbus/include/windows/Hcanbus.h") + else() + set(_LH_CANFD_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/libcanbus/include/linux/Hcanbus.h") + endif() + set(_LH_CANFD_TP_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/libcanbus") + # 兜底再看安装位置 third_party + if((NOT EXISTS "${_LH_CANFD_HEADER}" OR NOT EXISTS "${_LH_CANFD_TP_ROOT}") + AND NOT LINKERHAND_AS_SUBPROJECT AND LINKERHAND_INCLUDE_DIR) + get_filename_component(_LH_PREFIX "${LINKERHAND_INCLUDE_DIR}" DIRECTORY) + get_filename_component(_LH_PREFIX "${_LH_PREFIX}" DIRECTORY) + set(_LH_INSTALLED_TP "${_LH_PREFIX}/lib/linkerhand-cpp-sdk/third_party/libcanbus") + if(EXISTS "${_LH_INSTALLED_TP}") + set(_LH_CANFD_TP_ROOT "${_LH_INSTALLED_TP}") + endif() + if(WIN32) + set(_LH_INSTALLED_HDR "${_LH_INSTALLED_TP}/include/windows/Hcanbus.h") + else() + set(_LH_INSTALLED_HDR "${_LH_INSTALLED_TP}/include/linux/Hcanbus.h") + endif() + if(EXISTS "${_LH_INSTALLED_HDR}") + set(_LH_CANFD_HEADER "${_LH_INSTALLED_HDR}") + endif() + endif() + set(_LH_NEED_DOWNGRADE FALSE) + if(NOT EXISTS "${_LH_CANFD_HEADER}") + set(_LH_NEED_DOWNGRADE TRUE) + message(STATUS "examples: USE_CANFD downgrade reason: libcanbus header not found") + elseif(NOT LINKERHAND_AS_SUBPROJECT AND NOT EXISTS "${_LH_CANFD_TP_ROOT}") + # 头能找到但 third_party/libcanbus 整体不在场:发布包多半是 USE_CANFD=OFF 构建的, + # 此时 SDK .so 不含 CanFD 符号,继续编 canfd 示例必链接失败。 + set(_LH_NEED_DOWNGRADE TRUE) + message(STATUS "examples: USE_CANFD downgrade reason: third_party/libcanbus not shipped with this SDK") + endif() + # 终极判据:直接查 SDK .so 是否真带 CanFD 符号。下游有时会自己把 third_party/libcanbus + # 补回来,但发布的 .so 仍是 USE_CANFD=OFF 编出来的——这里能兜住这种"头补了 lib 没补"的坑。 + # 仅 Linux/macOS 走 nm;Windows 暂不做,继续依赖前两条。 + if(NOT _LH_NEED_DOWNGRADE AND NOT LINKERHAND_AS_SUBPROJECT + AND UNIX AND NOT APPLE AND LINKERHAND_LIB_DIR) + set(_LH_SDK_SO "") + foreach(_n linkerhand_cpp_sdk.so.2.0.0 linkerhand_cpp_sdk.so.2 linkerhand_cpp_sdk.so) + if(NOT _LH_SDK_SO AND EXISTS "${LINKERHAND_LIB_DIR}/${_n}") + set(_LH_SDK_SO "${LINKERHAND_LIB_DIR}/${_n}") + endif() + endforeach() + if(_LH_SDK_SO) + execute_process( + COMMAND sh -c "nm -D --defined-only '${_LH_SDK_SO}' 2>/dev/null | grep -q CanFD" + RESULT_VARIABLE _LH_CANFD_SYM_RC + ) + if(NOT _LH_CANFD_SYM_RC EQUAL 0) + set(_LH_NEED_DOWNGRADE TRUE) + message(STATUS "examples: USE_CANFD downgrade reason: SDK lib does not export CanFD symbols (${_LH_SDK_SO})") + endif() + endif() + unset(_LH_SDK_SO) + unset(_LH_CANFD_SYM_RC) + endif() + if(_LH_NEED_DOWNGRADE) + message(WARNING "USE_CANFD=ON but CAN-FD prerequisites missing; downgrading USE_CANFD=OFF for examples.") + set(USE_CANFD OFF CACHE BOOL "Enable CAN-FD examples and libcanbus runtime deps" FORCE) + endif() + unset(_LH_CANFD_HEADER) + unset(_LH_CANFD_TP_ROOT) + unset(_LH_INSTALLED_TP) + unset(_LH_INSTALLED_HDR) + unset(_LH_NEED_DOWNGRADE) +endif() + if(WIN32) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../third_party/PCAN_Basic/Include) - include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../third_party/libcanbus/include/windows) + if(USE_CANFD) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../third_party/libcanbus/include/windows) + endif() else() - include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../third_party/libcanbus/include/linux) - - # Linux: 设置 libcanbus 库路径 - set(LIBCANBUS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/libcanbus/linux") - if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64") - set(LIBCANBUS_DIR "${LIBCANBUS_DIR}/arm64") - else() - set(LIBCANBUS_DIR "${LIBCANBUS_DIR}/ubuntu22") + if(USE_CANFD) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../third_party/libcanbus/include/linux) + + # Linux: 设置 libcanbus 库路径(按发行版选 ubuntu20/22,与顶层 CMakeLists 一致) + set(LIBCANBUS_BASE "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/libcanbus/linux") + if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64") + set(LIBCANBUS_DIR "${LIBCANBUS_BASE}/arm64") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm") + set(LIBCANBUS_DIR "${LIBCANBUS_BASE}/arm") + else() + # 嵌套构建时继承顶层 UBUNTU_VERSION_MAJOR;独立构建则就地读 /etc/os-release + if(NOT DEFINED UBUNTU_VERSION_MAJOR OR UBUNTU_VERSION_MAJOR STREQUAL "") + if(EXISTS "/etc/os-release") + file(STRINGS "/etc/os-release" _LH_VID_LINE REGEX "^VERSION_ID=") + if(_LH_VID_LINE MATCHES "([0-9]+)") + set(UBUNTU_VERSION_MAJOR "${CMAKE_MATCH_1}") + endif() + endif() + endif() + if(UBUNTU_VERSION_MAJOR AND UBUNTU_VERSION_MAJOR GREATER_EQUAL 22 AND + EXISTS "${LIBCANBUS_BASE}/ubuntu22/libcanbus.so") + set(LIBCANBUS_DIR "${LIBCANBUS_BASE}/ubuntu22") + elseif(EXISTS "${LIBCANBUS_BASE}/ubuntu20/libcanbus.so") + set(LIBCANBUS_DIR "${LIBCANBUS_BASE}/ubuntu20") + else() + set(LIBCANBUS_DIR "${LIBCANBUS_BASE}/ubuntu22") + endif() + endif() + + # 添加库搜索路径 + link_directories(${LIBCANBUS_DIR}) + # libcanbus.so 是 SDK .so 的间接(NEEDED)依赖;ld 解析间接依赖只认 rpath-link, + # 不认 -L/link_directories,故显式补 -rpath-link,避免 "libcanbus.so ... not found" + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath-link,${LIBCANBUS_DIR}") + message(STATUS "libcanbus directory: ${LIBCANBUS_DIR}") endif() - - # 添加库搜索路径 - link_directories(${LIBCANBUS_DIR}) - - # 设置运行时库路径,确保运行时能找到 libcanbus.so 与 SDK .so + + # 设置运行时库路径,确保运行时能找到 SDK .so(以及 USE_CANFD 时的 libcanbus.so) # $ORIGIN: 让 exe 在自身所在目录(bin/)查找,从而拿到 copy_dependencies 复制过来的 SDK .so - set(CMAKE_INSTALL_RPATH "\$ORIGIN" "${LIBCANBUS_DIR}") + set(CMAKE_INSTALL_RPATH "\$ORIGIN") + if(USE_CANFD AND LIBCANBUS_DIR) + list(APPEND CMAKE_INSTALL_RPATH "${LIBCANBUS_DIR}") + endif() if(LINKERHAND_LIB_DIR) list(APPEND CMAKE_INSTALL_RPATH "${LINKERHAND_LIB_DIR}") endif() set(CMAKE_BUILD_WITH_INSTALL_RPATH ON) - - message(STATUS "libcanbus directory: ${LIBCANBUS_DIR}") endif() # 兜底(独立消费已安装 SDK,且示例被拷出源码树):源码树 ../third_party 不存在时, @@ -145,97 +260,122 @@ if(NOT LINKERHAND_AS_SUBPROJECT AND LINKERHAND_INCLUDE_DIR) endif() endif() -function(copy_dependencies target) +# 所有 example 共享同一份运行时依赖:用单一 custom target 一次性拷贝, +# 而不是给每个 exe 挂 POST_BUILD —— 否则 ~30 个 example 会重复刷屏并和 +# SONAME 软链 target 在并行构建下争抢同一份文件。 +function(_define_examples_runtime_deps_target) + if(TARGET examples_runtime_deps) + return() + endif() + + set(_cmds "") + if(LINKERHAND_AS_SUBPROJECT) - # 嵌套模式:用 generator expression 取主项目 SDK target 的产物 - add_custom_command(TARGET ${target} POST_BUILD + list(APPEND _cmds COMMAND ${CMAKE_COMMAND} -E copy_if_different "$" - "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" - COMMENT "Copying linkerhand_cpp_sdk runtime to output directory" - ) - # Unix: 复制只拿到 .so.,exe NEEDED 的是 .so.(由 SONAME 决定), - # 必须在 bin/ 里同步建出 SONAME 软链,否则 ld.so 找不到。 - if(UNIX AND NOT APPLE) - add_custom_command(TARGET ${target} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E remove -f - "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$" - COMMAND ${CMAKE_COMMAND} -E create_symlink - "$" - "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$" - COMMENT "Creating SONAME symlink for linkerhand_cpp_sdk in output directory" - ) - endif() + "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") elseif(LINKERHAND_DLL AND EXISTS "${LINKERHAND_DLL}") - add_custom_command(TARGET ${target} POST_BUILD + list(APPEND _cmds COMMAND ${CMAKE_COMMAND} -E copy_if_different "${LINKERHAND_DLL}" - "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" - COMMENT "Copying ${LINKERHAND_DLL_NAME} to output directory" - ) + "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") endif() if(WIN32) set(PCAN_DLL "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/PCAN_Basic/x64/PCANBasic.dll") if(EXISTS "${PCAN_DLL}") - add_custom_command(TARGET ${target} POST_BUILD + list(APPEND _cmds COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${PCAN_DLL}" - "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" - COMMENT "Copying PCANBasic.dll to output directory" - ) + "${PCAN_DLL}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") endif() set(CANFD_DLL "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/libcanbus/windows/x64/HCanbus.dll") - if(EXISTS "${CANFD_DLL}") - add_custom_command(TARGET ${target} POST_BUILD + if(USE_CANFD AND EXISTS "${CANFD_DLL}") + list(APPEND _cmds COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${CANFD_DLL}" - "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" - COMMENT "Copying HCanbus.dll (CANFD) to output directory" - ) + "${CANFD_DLL}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") endif() # 拷贝 mingw runtime DLL:SDK DLL 与 exe 必须共享同一份 libstdc++, # 否则跨 DLL 边界传 std::string / std::vector 会因 allocator 不一致引发 # heap corruption;exe 启动若找不到 runtime 也会被 Windows 静默终止。 - # win32 线程模型下 std::mutex / std::thread 用 Windows 原生 API, - # 不依赖 libwinpthread-1.dll,所以不在拷贝列表中。 - # 仅当 lib/win64/mingw/ 下存在这些 DLL 时拷贝(pack.sh 出品的 release 包带这些)。 + # win32 线程模型不依赖 libwinpthread-1.dll,所以不在拷贝列表中。 foreach(_RT_DLL libstdc++-6.dll libgcc_s_seh-1.dll) set(_RT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../lib/win64/mingw/${_RT_DLL}") if(EXISTS "${_RT_PATH}") - add_custom_command(TARGET ${target} POST_BUILD + list(APPEND _cmds COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${_RT_PATH}" - "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" - COMMENT "Copying ${_RT_DLL} (mingw runtime) to output directory" - ) + "${_RT_PATH}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") endif() endforeach() elseif(UNIX AND NOT APPLE) - # Linux: 复制 libcanbus.so 到输出目录 - set(CANBUS_SO "${LIBCANBUS_DIR}/libcanbus.so") - if(EXISTS "${CANBUS_SO}") - add_custom_command(TARGET ${target} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${CANBUS_SO}" - "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" - COMMENT "Copying libcanbus.so to output directory" - ) - endif() - - # Linux: 复制 libusb-1.0.so 到输出目录(libcanbus 的依赖) - set(LIBUSB_SO "${LIBCANBUS_DIR}/libusb-1.0.so") - if(EXISTS "${LIBUSB_SO}") - add_custom_command(TARGET ${target} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${LIBUSB_SO}" - "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" - COMMENT "Copying libusb-1.0.so to output directory" - ) + if(USE_CANFD AND DEFINED LIBCANBUS_DIR) + set(CANBUS_SO "${LIBCANBUS_DIR}/libcanbus.so") + if(EXISTS "${CANBUS_SO}") + list(APPEND _cmds + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${CANBUS_SO}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") + endif() + # libusb-1.0.so 是 libcanbus 的依赖 + set(LIBUSB_SO "${LIBCANBUS_DIR}/libusb-1.0.so") + if(EXISTS "${LIBUSB_SO}") + list(APPEND _cmds + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${LIBUSB_SO}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") + endif() endif() endif() + + if(LINKERHAND_AS_SUBPROJECT) + add_custom_target(examples_runtime_deps ALL + ${_cmds} + DEPENDS linkerhand_cpp_sdk + COMMENT "Staging examples runtime dependencies into ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" + VERBATIM + ) + else() + add_custom_target(examples_runtime_deps ALL + ${_cmds} + COMMENT "Staging examples runtime dependencies into ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" + VERBATIM + ) + endif() +endfunction() + +# Unix: 复制只拿到 .so.,exe NEEDED 的是 .so.(由 SONAME 决定), +# 必须在 bin/ 里同步建出 SONAME 软链,否则 ld.so 找不到。 +# 用单一 custom target 而非 per-example POST_BUILD:并行构建下多 target +# 同时跑 remove + create_symlink 会出现 "File exists" 竞态。 +function(_define_examples_sdk_soname_symlink_target) + if(TARGET examples_sdk_soname_symlink) + return() + endif() + if(NOT LINKERHAND_AS_SUBPROJECT) + return() + endif() + if(NOT (UNIX AND NOT APPLE)) + return() + endif() + add_custom_target(examples_sdk_soname_symlink ALL + COMMAND ${CMAKE_COMMAND} -E remove -f + "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$" + COMMAND ${CMAKE_COMMAND} -E create_symlink + "$" + "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$" + DEPENDS linkerhand_cpp_sdk + COMMENT "Creating SONAME symlink for linkerhand_cpp_sdk in output directory" + VERBATIM + ) +endfunction() + +function(copy_dependencies target) + _define_examples_runtime_deps_target() + _define_examples_sdk_soname_symlink_target() + add_dependencies(${target} examples_runtime_deps) + if(TARGET examples_sdk_soname_symlink) + add_dependencies(${target} examples_sdk_soname_symlink) + endif() endfunction() set(COMMON_LIBS ${LINKERHAND_LIB}) @@ -255,7 +395,7 @@ if(BUILD_TESTS) include(${CMAKE_CURRENT_SOURCE_DIR}/sources.cmake) set(EXAMPLE_STEMS ${LINKERHAND_EXAMPLES}) - if(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64") + if(USE_CANFD AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64") list(APPEND EXAMPLE_STEMS ${LINKERHAND_EXAMPLES_CANFD}) endif() diff --git a/examples/sources.cmake b/examples/sources.cmake index 4145933..943d42c 100644 --- a/examples/sources.cmake +++ b/examples/sources.cmake @@ -26,9 +26,10 @@ set(LINKERHAND_EXAMPLES test_o6_modbus_2 L10/action_group_show range_to_arc/range_to_arc + test_conversion ) -# 需要 CanFD 支持的示例:仅在非 aarch64 的 Linux 上构建(与顶层 ENABLE_CANFD 逻辑一致) +# 需要 CanFD 支持的示例:仅在非 aarch64 的 Linux 且 USE_CANFD=ON 时构建 set(LINKERHAND_EXAMPLES_CANFD test_o20_canfd_0 test_o20_canfd_1 diff --git a/examples/test_conversion.cpp b/examples/test_conversion.cpp new file mode 100644 index 0000000..e05a2b4 --- /dev/null +++ b/examples/test_conversion.cpp @@ -0,0 +1,203 @@ +// L20 / CAN / 左手 —— 读取状态/触觉并执行握拳与张开(带 TX/RX 日志) +#include +#include + +#include "_win_console_utf8.h" +#include "LinkerHandApi.h" +#include "CommFactory.h" + +// 格式化当前时间,用于 TX/RX 日志 +std::string getCurrentTime() { + auto now = std::chrono::system_clock::now(); + auto now_time = std::chrono::system_clock::to_time_t(now); + auto now_ms = std::chrono::duration_cast( + now.time_since_epoch()) % 1000; + + #if defined(_WIN32) || defined(_WIN64) + std::tm tm_info = {}; + localtime_s(&tm_info, &now_time); + std::tm* tm = &tm_info; + #else + std::tm* tm = std::localtime(&now_time); + #endif + char buffer[80]; + std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", tm); + + std::ostringstream oss; + oss << buffer << "." << std::setfill('0') << std::setw(3) << now_ms.count(); + return oss.str(); +} + +double scale_value(double original_value, double a_min, double a_max, double b_min, double b_max) +{ + return (original_value - a_min) * (b_max - b_min) / (a_max - a_min) + b_min; +} + +int main() { + try { + // 初始化灵巧手 + LinkerHandApi hand(LINKER_HAND::G20, HAND_TYPE::LEFT); + + // 创建 CAN 总线对象 + std::shared_ptr bus = Communication::CommFactory::createCanBus("can0", 1000000); + + hand.setCanTxCallback([bus](uint32_t can_id, const uint8_t *data, uintptr_t data_len) -> int32_t { + static int tx_count = 0; + tx_count++; + /* + std::cout << "\033[32m[TX #" << tx_count << "]\033[0m " + << getCurrentTime() + << " | CAN_ID: 0x" << std::hex << std::setw(8) << std::setfill('0') << can_id + << std::dec << " (" << can_id << ")" + << " | Len: " << data_len + << " | Data: "; + for (uintptr_t i = 0; i < data_len; i++) { + std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)data[i] << " "; + } + std::cout << std::dec << std::endl; +*/ + std::vector data_vec(data, data + data_len); + bus->send(data_vec, can_id); + return 0; + }); + + hand.setCanRxCallback([bus](uint32_t* can_id_out, uint8_t* data_out, uint8_t* len_out) -> int32_t { + auto frame = bus->recv(); + if (frame.can_id == 0 && frame.can_dlc == 0) { + return -1; + } + *can_id_out = frame.can_id; + *len_out = frame.can_dlc; + memcpy(data_out, frame.data, frame.can_dlc); + + static int rx_count = 0; + rx_count++; + /* + std::cout << "\033[34m[RX #" << rx_count << "]\033[0m " + << getCurrentTime() + << " | CAN_ID: 0x" << std::hex << std::setw(8) << std::setfill('0') << frame.can_id + << std::dec << " (" << frame.can_id << ")" + << " | DLC: " << (int)frame.can_dlc + << " | Data: "; + + for (int i = 0; i < frame.can_dlc; i++) { + std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)frame.data[i] << " "; + } + std::cout << std::dec << std::endl; + */ + return 0; + }); + + // for (size_t i = 0; i < 1; ++i) { + // std::cout << hand.getVersion() << std::endl; + + // std::vector torque = hand.getTorque(); + // std::cout << "torque size:" << torque.size() << std::endl; + // for (size_t j = 0; j < torque.size(); j++) { + // std::cout << (int)torque[j] << " "; + // } + // std::cout << std::endl; + + // std::vector speed = hand.getSpeed(); + // std::cout << "speed size:" << speed.size() << std::endl; + // for (size_t j = 0; j < speed.size(); j++) { + // std::cout << (int)speed[j] << " "; + // } + // std::cout << std::endl; + + // std::vector state = hand.getPosition(); + // std::cout << "state size:" << state.size() << std::endl; + // for (size_t j = 0; j < state.size(); j++) { + // std::cout << (int)state[j] << " "; + // } + // std::cout << std::endl; + + // std::vector>> touch_mats = hand.getForce(); + // // 手指名称表,顺序对应 touch_mats[0..4] + // const std::array finger_name = { + // "THUMB_TOUCH", "INDEX_TOUCH", "MIDDLE_TOUCH", "RING_TOUCH", "LITTLE_TOUCH" + // }; + // for (size_t n = 0; n < touch_mats.size(); ++n) { + // std::cout << finger_name[n] << ":\n"; + // for (const auto &row : touch_mats[n]) { + // for (uint8_t val : row) + // std::cout << std::setw(2) << static_cast(val) << ' '; + // std::cout << '\n'; + // } + // std::cout << '\n'; + // } + + // // 握拳 + // std::vector fist_pose = {74, 0, 0, 0, 0, 176, 52, 120, 150, 201, 198, 0, 0, 0, 0, 254, 0, 0, 0, 0, 100, 0, 0, 0, 0}; + // hand.setPosition(fist_pose); + // std::this_thread::sleep_for(std::chrono::seconds(1)); + + // // 张开 + // std::vector open_pose = {74, 254, 254, 254, 253, 176, 52, 120, 150, 201, 198, 0, 0, 0, 0, 254, 0, 0, 0, 0, 254, 255, 255, 255, 250}; + // hand.setPosition(open_pose); + // std::this_thread::sleep_for(std::chrono::seconds(1)); + // } + + + std::cout << hand.getVersion() << std::endl; + + // 张开 + std::vector open_pose = {255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255}; + hand.setPosition(open_pose); + + // std::vector op_arc = {0.0, 0.0, 0.0, 0.0, 0.0, -0.5, -0.18, 0.03, 0.11, 0.18, 0.15, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + // hand.setPositionArc(op_arc); + std::this_thread::sleep_for(std::chrono::seconds(1)); + + std::cout << "setPosition : "; + for (auto &item:open_pose) { + std::cout << (int)item << " "; + } + std::cout << std::endl; + + + std::cout << "-------------------------------------" << std::endl; + + + for (size_t i = 0; i < 2; i++) { + std::vector pos_1 = hand.getPosition(); + std::cout << "getPosition : "; + for (auto &item:pos_1) { + std::cout << (int)item << " "; + } + std::cout << std::endl; + } + + + std::cout << "-------------------------------------" << std::endl; + + + + std::vector pos_2 = hand.testPositionArc(open_pose); + std::cout << "testPositionArc size:" << pos_2.size() << std::endl; + for (size_t j = 0; j < pos_2.size(); j++) { + std::cout << (float)pos_2[j] << " "; + } + std::cout << std::endl; + + std::cout << "-------------------------------------" << std::endl; + + std::vector state = hand.getPositionArc(); + std::cout << "getPositionArc size:" << state.size() << std::endl; + for (size_t j = 0; j < state.size(); j++) { + std::cout << (float)state[j] << " "; + } + std::cout << std::endl; + + + // std::vector fist_pose = {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}; + // hand.setPositionArc(fist_pose); + // std::this_thread::sleep_for(std::chrono::seconds(1)); + + + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } + return 0; +} diff --git a/include/communication/CommFactory.h b/include/communication/CommFactory.h index 55c0cb8..64d9b7a 100644 --- a/include/communication/CommFactory.h +++ b/include/communication/CommFactory.h @@ -9,8 +9,10 @@ #include "communication/ICanBus.h" #include "communication/CanBus.h" #include "communication/PCANBus.h" +#if LINKERHAND_USE_CANFD #include "communication/ICanFD.h" #include "communication/CanFD.h" +#endif #include "communication/IModbus.h" #include "communication/Modbus.h" #if USE_ETHERCAT @@ -101,6 +103,8 @@ namespace communication // ====================== CAN FD ====================== // CanFD 类跨平台;构造参数 (dev_num, ch_num) 由 third_party libcanbus 定义。 + // 仅在 SDK 构建时 USE_CANFD=ON 才声明;OFF 时调用站点编译期可见缺失,便于排错。 + #if LINKERHAND_USE_CANFD static std::unique_ptr createCanFD(uint32_t dev_num, uint32_t ch_num) { return std::unique_ptr(new CanFD(dev_num, ch_num)); @@ -117,6 +121,7 @@ namespace communication } return std::unique_ptr(new CanFD(0, ch)); } + #endif // LINKERHAND_USE_CANFD // ====================== Modbus RTU ====================== diff --git a/lib/linux/arm64/linkerhand_cpp_sdk.so.2.0.0 b/lib/linux/arm64/linkerhand_cpp_sdk.so.2.0.0 index ff2e340..79352eb 100644 Binary files a/lib/linux/arm64/linkerhand_cpp_sdk.so.2.0.0 and b/lib/linux/arm64/linkerhand_cpp_sdk.so.2.0.0 differ diff --git a/lib/linux/x86_64/linkerhand_cpp_sdk.so.2.0.0 b/lib/linux/x86_64/linkerhand_cpp_sdk.so.2.0.0 index fbe93e1..d135240 100644 Binary files a/lib/linux/x86_64/linkerhand_cpp_sdk.so.2.0.0 and b/lib/linux/x86_64/linkerhand_cpp_sdk.so.2.0.0 differ diff --git a/lib/win64/mingw/linkerhand_cpp_sdk.dll b/lib/win64/mingw/linkerhand_cpp_sdk.dll index 5b4af3e..a497008 100644 Binary files a/lib/win64/mingw/linkerhand_cpp_sdk.dll and b/lib/win64/mingw/linkerhand_cpp_sdk.dll differ diff --git a/lib/win64/msvc/linkerhand_cpp_sdk.dll b/lib/win64/msvc/linkerhand_cpp_sdk.dll index aedcd40..aa7f786 100644 Binary files a/lib/win64/msvc/linkerhand_cpp_sdk.dll and b/lib/win64/msvc/linkerhand_cpp_sdk.dll differ diff --git a/lib/win64/msvc/linkerhand_cpp_sdk.lib b/lib/win64/msvc/linkerhand_cpp_sdk.lib index f560758..fc129d1 100644 Binary files a/lib/win64/msvc/linkerhand_cpp_sdk.lib and b/lib/win64/msvc/linkerhand_cpp_sdk.lib differ