Commit 723933d0 authored by 唐永康's avatar 唐永康

Initial commit

parents
---
Language: Cpp
BasedOnStyle: LLVM
IndentWidth: 4
TabWidth: 4
UseTab: Never
ColumnLimit: 100
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortBlocksOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: false
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: true
BeforeElse: true
IndentBraces: false
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
CommentPragmas: '^ IWYU pragma:'
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
- Regex: '.*'
Priority: 1
IncludeIsMainRegex: '([-_](test|unittest))?$'
IndentCaseLabels: true
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: false
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakAssignment: 2
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Right
ReflowComments: true
SortIncludes: true
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: C++17
---
Checks: >
-*,
bugprone-*,
cert-*,
cppcoreguidelines-*,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-avoid-non-const-global-variables,
misc-*,
-misc-non-private-member-variables-in-classes,
performance-*,
-performance-avoid-endl,
readability-*,
-readability-magic-numbers,
-readability-identifier-length,
-readability-function-cognitive-complexity,
modernize-*,
-modernize-use-trailing-return-type,
-modernize-avoid-c-arrays
WarningsAsErrors: ''
HeaderFilterRegex: '.*'
AnalyzeTemporaryDtors: false
FormatStyle: 'file'
User: ''
CheckOptions:
- key: readability-identifier-naming.NamespaceCase
value: lower_case
- key: readability-identifier-naming.ClassCase
value: CamelCase
- key: readability-identifier-naming.StructCase
value: CamelCase
- key: readability-identifier-naming.FunctionCase
value: camelBack
- key: readability-identifier-naming.VariableCase
value: camelBack
- key: readability-identifier-naming.ParameterCase
value: camelBack
- key: readability-identifier-naming.MemberCase
value: camelBack
- key: readability-identifier-naming.PrivateMemberPrefix
value: m_
- key: readability-identifier-naming.ProtectedMemberPrefix
value: m_
- key: readability-identifier-naming.EnumCase
value: CamelCase
- key: readability-identifier-naming.EnumConstantCase
value: UPPER_CASE
- key: readability-identifier-naming.ConstantCase
value: UPPER_CASE
- key: readability-identifier-naming.MacroDefinitionCase
value: UPPER_CASE
- key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor
value: true
- key: cppcoreguidelines-special-member-functions.AllowMissingMoveFunctions
value: true
# 自动检测文本文件并规范化行尾
* text=auto
# 明确指定文本文件使用 LF 换行符
*.cpp text eol=lf
*.h text eol=lf
*.hpp text eol=lf
*.c text eol=lf
*.cmake text eol=lf
CMakeLists.txt text eol=lf
*.txt text eol=lf
*.md text eol=lf
*.sh text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
*.json text eol=lf
# 二进制文件
*.so binary
*.dll binary
*.dylib binary
*.a binary
*.lib binary
*.exe binary
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.pdf binary
# 第三方库目录保持原样
third_party/** -text
name: CI/CD
on:
push:
branches: [ main, master, develop ]
pull_request:
branches: [ main, master, develop ]
workflow_dispatch:
env:
BUILD_TYPE: Release
jobs:
build-and-test:
name: Build and Test - ${{ matrix.os }} (${{ matrix.arch }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# Linux x86_64 (主要测试平台)
- os: ubuntu-latest
arch: x86_64
cmake_build_type: Release
use_qemu: false
# Linux x86_64 Debug 构建
- os: ubuntu-latest
arch: x86_64
cmake_build_type: Debug
use_qemu: false
# Linux aarch64 Release 构建 (使用 QEMU 模拟)
- os: ubuntu-latest
arch: aarch64
cmake_build_type: Release
use_qemu: true
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 设置 QEMU (aarch64)
if: matrix.use_qemu == true
uses: docker/setup-qemu-action@v3
with:
platforms: arm64
- name: 缓存 CMake 构建
uses: actions/cache@v4
with:
path: |
build
~/.cmake
key: ${{ runner.os }}-cmake-${{ matrix.arch }}-${{ matrix.cmake_build_type }}-${{ hashFiles('**/CMakeLists.txt') }}
restore-keys: |
${{ runner.os }}-cmake-${{ matrix.arch }}-${{ matrix.cmake_build_type }}-
- name: 安装构建依赖
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential \
cmake \
git \
libpthread-stubs0-dev
# 对于 aarch64,安装交叉编译工具链或 QEMU 用户模式
if [ "${{ matrix.arch }}" = "aarch64" ]; then
sudo apt-get install -y \
gcc-aarch64-linux-gnu \
g++-aarch64-linux-gnu \
qemu-user-static \
binfmt-support
fi
- name: 验证工具链版本
run: |
echo "=== CMake 版本 ==="
cmake --version
echo ""
echo "=== 编译器版本 ==="
if [ "${{ matrix.arch }}" = "aarch64" ]; then
aarch64-linux-gnu-gcc --version
aarch64-linux-gnu-g++ --version
else
gcc --version
g++ --version
fi
echo ""
echo "=== 系统信息 ==="
uname -a
echo "目标架构: ${{ matrix.arch }}"
echo "实际架构: $(uname -m)"
- name: 创建构建目录
run: mkdir -p build
- name: 配置 CMake
working-directory: build
run: |
if [ "${{ matrix.arch }}" = "aarch64" ]; then
# 使用交叉编译工具链
cmake .. \
-DCMAKE_BUILD_TYPE=${{ matrix.cmake_build_type }} \
-DBUILD_TESTING=ON \
-DCMAKE_SYSTEM_NAME=Linux \
-DCMAKE_SYSTEM_PROCESSOR=aarch64 \
-DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc \
-DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++ \
-DCMAKE_FIND_ROOT_PATH=/usr/aarch64-linux-gnu
else
cmake .. \
-DCMAKE_BUILD_TYPE=${{ matrix.cmake_build_type }} \
-DBUILD_TESTING=ON
fi
- name: 构建项目
working-directory: build
run: |
cmake --build . \
--config ${{ matrix.cmake_build_type }} \
--parallel $(nproc) \
--verbose
- name: 运行测试
working-directory: build
run: |
if [ "${{ matrix.arch }}" = "aarch64" ]; then
# 对于 aarch64,使用 QEMU 运行测试
# 设置 QEMU 环境变量
export QEMU_LD_PREFIX=/usr/aarch64-linux-gnu
# 使用较少的并行任务,因为 QEMU 模拟较慢
ctest --output-on-failure --parallel 2 || ctest --output-on-failure
else
ctest --output-on-failure --parallel $(nproc) || ctest --output-on-failure
fi
continue-on-error: false
- name: 显示测试结果
if: always()
working-directory: build
run: |
echo "=== 测试摘要 ==="
ctest --print-labels || true
echo ""
echo "=== 构建产物 ==="
find . -type f -executable -name "test_*" -exec file {} \; || true
find . -type f -executable -name "*example*" -exec file {} \; || true
build-examples:
name: Build Examples
runs-on: ubuntu-latest
needs: build-and-test
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 安装构建依赖
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential \
cmake \
git \
libpthread-stubs0-dev
- name: 创建构建目录
run: mkdir -p build
- name: 配置 CMake
working-directory: build
run: |
cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_TESTING=OFF
- name: 构建示例程序
working-directory: build
run: |
cmake --build . \
--config Release \
--parallel $(nproc) \
--target toolset_example action_group_show_l10
- name: 验证示例程序
working-directory: build
run: |
echo "=== 验证构建产物 ==="
if [ -f toolset_example ]; then
echo "✓ toolset_example 构建成功"
file toolset_example
ls -lh toolset_example
else
echo "✗ toolset_example 未找到"
exit 1
fi
if [ -f action_group_show_l10 ]; then
echo "✓ action_group_show_l10 构建成功"
file action_group_show_l10
ls -lh action_group_show_l10
else
echo "✗ action_group_show_l10 未找到"
exit 1
fi
code-quality:
name: Code Quality Checks
runs-on: ubuntu-latest
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 检查文件编码和换行符
run: |
echo "检查文件编码和换行符..."
# 检查是否有 Windows 换行符(排除第三方库目录)
CRLF_FILES=$(find . -type f \
! -path "./third_party/*" \
\( -name "*.cpp" -o -name "*.h" -o -name "*.cmake" -o -name "CMakeLists.txt" \) \
-exec file {} \; | grep -i "CRLF" || true)
if [ -n "$CRLF_FILES" ]; then
echo "错误: 发现 Windows 换行符 (CRLF)"
echo ""
echo "以下文件需要修复:"
echo "$CRLF_FILES"
echo ""
echo "修复方法(Linux/macOS):"
echo " find . -type f ! -path './third_party/*' \\( -name '*.cpp' -o -name '*.h' -o -name '*.cmake' -o -name 'CMakeLists.txt' \\) -exec sed -i 's/\\r$//' {} \\;"
echo ""
echo "或者使用 dos2unix:"
echo " find . -type f ! -path './third_party/*' \\( -name '*.cpp' -o -name '*.h' -o -name '*.cmake' -o -name 'CMakeLists.txt' \\) -exec dos2unix {} \\;"
exit 1
fi
echo "✓ 文件编码检查通过(已排除 third_party 目录)"
- name: 检查 CMake 语法
run: |
echo "检查 CMake 语法..."
cmake --version
# 尝试解析 CMakeLists.txt
mkdir -p /tmp/cmake-check
cd /tmp/cmake-check
cmake ${{ github.workspace }} -Wno-dev > /dev/null 2>&1 || true
echo "✓ CMake 语法检查完成"
- name: 检查头文件包含
run: |
echo "检查头文件包含..."
# 检查是否有明显的头文件问题
if find ${{ github.workspace }}/include -name "*.h" -exec grep -l "#include.*\.\./" {} \; | head -5; then
echo "警告: 发现相对路径包含"
fi
echo "✓ 头文件检查完成"
summary:
name: CI Summary
runs-on: ubuntu-latest
needs: [build-and-test, build-examples, code-quality]
if: always()
steps:
- name: 生成 CI 摘要
run: |
echo "## CI/CD 构建摘要" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 构建状态" >> $GITHUB_STEP_SUMMARY
echo "- 构建和测试: ${{ needs.build-and-test.result }}" >> $GITHUB_STEP_SUMMARY
echo "- 示例构建: ${{ needs.build-examples.result }}" >> $GITHUB_STEP_SUMMARY
echo "- 代码质量: ${{ needs.code-quality.result }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 详细信息" >> $GITHUB_STEP_SUMMARY
echo "查看上方的作业详情以获取更多信息。" >> $GITHUB_STEP_SUMMARY
build
.vscode/
linker_hand/build
TODO.md
PROJECT_ANALYSIS.md
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
LinkerHand-CPP-SDK
\ No newline at end of file
This diff is collapsed.
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>
\ No newline at end of file
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="CMake" type="CPP_MODULE" version="4" />
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakePythonSetting">
<option name="pythonIntegrationState" value="YES" />
</component>
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/linkerhand-cpp-sdk.iml" filepath="$PROJECT_DIR$/.idea/linkerhand-cpp-sdk.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
\ No newline at end of file
cmake_minimum_required(VERSION 3.15)
project(LinkerHand-CPP-SDK
VERSION 1.1.7
LANGUAGES CXX
DESCRIPTION "LinkerHand C++ SDK for robotic hand control"
)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
set(LIB_SUBDIR "x86_64")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
set(LIB_SUBDIR "aarch64")
else()
message(WARNING "Unknown architecture, defaulting to x86_64")
set(LIB_SUBDIR "x86_64")
endif()
#-----------------------------------------------------------------------------
# LINKER_HAND_CPP_SDK
#-----------------------------------------------------------------------------
find_library(LINKER_HAND_LIB
NAMES linkerhand_cpp_sdk linkerhand_cpp
PATHS ${CMAKE_CURRENT_SOURCE_DIR}/lib/${LIB_SUBDIR}
/usr/local/lib/linkerhand-cpp-sdk/${LIB_SUBDIR}
/usr/lib/linkerhand-cpp-sdk/${LIB_SUBDIR}
${CMAKE_INSTALL_PREFIX}/lib/linkerhand-cpp-sdk/${LIB_SUBDIR}
NO_DEFAULT_PATH
)
set(LINKER_HAND_INCLUDE_DIR
${CMAKE_CURRENT_SOURCE_DIR}/include
/usr/local/include/linkerhand-cpp-sdk
/usr/include/linkerhand-cpp-sdk
${CMAKE_INSTALL_PREFIX}/include/linkerhand-cpp-sdk
)
if(NOT LINKER_HAND_LIB)
message(FATAL_ERROR "linkerhand_cpp_sdk library not found!")
endif()
if(NOT LINKER_HAND_INCLUDE_DIR)
message(FATAL_ERROR "LinkerHand headers not found!")
endif()
message(STATUS "Found linkerhand_cpp_sdk library: ${LINKER_HAND_LIB}")
message(STATUS "Found LinkerHand headers: ${LINKER_HAND_INCLUDE_DIR}")
#-----------------------------------------------------------------------------
# INCLUDE_DIRECTORIES
#-----------------------------------------------------------------------------
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/include
${LINKER_HAND_INCLUDE_DIR}
)
#-----------------------------------------------------------------------------
# EXECUTABLE
#-----------------------------------------------------------------------------
add_executable(my_project src/main.cpp)
target_link_libraries(my_project ${LINKER_HAND_LIB} pthread)
add_executable(toolset_example examples/toolset_example.cpp)
target_link_libraries(toolset_example ${LINKER_HAND_LIB} pthread)
add_executable(action_group_show_l10 examples/action_group_show_l10.cpp)
target_link_libraries(action_group_show_l10 ${LINKER_HAND_LIB} pthread)
add_executable(o6_async_reader examples/o6_async_reader.cpp)
target_link_libraries(o6_async_reader ${LINKER_HAND_LIB} pthread)
#-----------------------------------------------------------------------------
# INSTALL TARGETS
#-----------------------------------------------------------------------------
# 安装头文件
install(DIRECTORY include/
DESTINATION include/linkerhand-cpp-sdk
FILES_MATCHING PATTERN "*.h"
)
# 安装库文件
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/lib/${LIB_SUBDIR})
install(DIRECTORY lib/${LIB_SUBDIR}/
DESTINATION lib/linkerhand-cpp-sdk/${LIB_SUBDIR}
FILES_MATCHING PATTERN "*.so*"
)
endif()
#-----------------------------------------------------------------------------
# PACKAGE CONFIGURATION
#-----------------------------------------------------------------------------
include(CMakePackageConfigHelpers)
# 创建包配置文件
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/LinkerHandCPPSDKConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY AnyNewerVersion
)
# 设置包配置变量(用于 configure_file)
set(PACKAGE_INCLUDE_INSTALL_DIR "include/linkerhand-cpp-sdk")
set(PACKAGE_LIB_INSTALL_DIR "lib/linkerhand-cpp-sdk/${LIB_SUBDIR}")
# 创建包配置文件模板
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/LinkerHandCPPSDKConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/LinkerHandCPPSDKConfig.cmake"
@ONLY
)
# 安装包配置文件
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/LinkerHandCPPSDKConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/LinkerHandCPPSDKConfigVersion.cmake"
DESTINATION lib/cmake/LinkerHandCPPSDK
)
#-----------------------------------------------------------------------------
# TESTING
#-----------------------------------------------------------------------------
# 添加选项以启用/禁用测试
option(BUILD_TESTING "Build the testing tree" ON)
# 如果启用测试,包含测试子目录
if(BUILD_TESTING)
add_subdirectory(tests)
endif()
MIT License
Copyright (c) 2026 灵心巧手(北京)科技有限公司
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
This diff is collapsed.
@PACKAGE_INIT@
# 确定系统架构
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
set(LIB_SUBDIR "x86_64")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
set(LIB_SUBDIR "aarch64")
else()
set(LIB_SUBDIR "x86_64")
endif()
# 设置包含目录和库目录(相对于安装前缀)
set(LinkerHandCPPSDK_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}/../../@PACKAGE_INCLUDE_INSTALL_DIR@")
set(LinkerHandCPPSDK_LIB_DIR "${CMAKE_CURRENT_LIST_DIR}/../../@PACKAGE_LIB_INSTALL_DIR@")
# 也支持从构建目录查找(用于开发时)
list(APPEND LinkerHandCPPSDK_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}/../../include/linkerhand-cpp-sdk")
list(APPEND LinkerHandCPPSDK_LIB_DIR "${CMAKE_CURRENT_LIST_DIR}/../../lib/linkerhand-cpp-sdk/${LIB_SUBDIR}")
# 查找库文件
find_library(LinkerHandCPPSDK_LIBRARY
NAMES linkerhand_cpp_sdk
PATHS ${LinkerHandCPPSDK_LIB_DIR}
NO_DEFAULT_PATH
)
# 检查库文件是否存在
if(NOT LinkerHandCPPSDK_LIBRARY)
message(FATAL_ERROR "LinkerHandCPPSDK library not found in ${LinkerHandCPPSDK_LIB_DIR}")
endif()
# 设置变量供外部使用
set(LinkerHandCPPSDK_FOUND TRUE)
set(LinkerHandCPPSDK_VERSION @PROJECT_VERSION@)
# 创建导入目标
if(NOT TARGET LinkerHandCPPSDK::LinkerHandCPPSDK)
add_library(LinkerHandCPPSDK::LinkerHandCPPSDK SHARED IMPORTED)
set_target_properties(LinkerHandCPPSDK::LinkerHandCPPSDK PROPERTIES
IMPORTED_LOCATION "${LinkerHandCPPSDK_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${LinkerHandCPPSDK_INCLUDE_DIR}"
)
endif()
check_required_components(LinkerHandCPPSDK)
This diff is collapsed.
# CI/CD 文档
本文档介绍项目的持续集成和持续部署(CI/CD)配置。
## 📋 目录
- [概述](#概述)
- [工作流配置](#工作流配置)
- [触发条件](#触发条件)
- [构建矩阵](#构建矩阵)
- [作业说明](#作业说明)
- [状态徽章](#状态徽章)
- [故障排查](#故障排查)
- [本地测试](#本地测试)
## 概述
项目使用 GitHub Actions 进行持续集成,自动执行以下任务:
- ✅ 多平台构建(Linux x86_64)
- ✅ 自动运行单元测试
- ✅ 构建示例程序
- ✅ 代码质量检查
- ✅ 构建状态报告
## 工作流配置
CI/CD 工作流配置文件位于:`.github/workflows/ci.yml`
### 主要特性
- **多构建配置**: 支持 Release 和 Debug 构建
- **并行测试**: 使用多核并行运行测试以加快速度
- **缓存优化**: 缓存 CMake 构建文件以提高效率
- **详细日志**: 提供详细的构建和测试输出
## 触发条件
工作流在以下情况下自动触发:
1. **推送代码** - 推送到 `main``master``develop` 分支
2. **Pull Request** - 创建或更新 PR 到 `main``master``develop` 分支
3. **手动触发** - 通过 GitHub Actions 界面手动运行(`workflow_dispatch`
## 构建矩阵
当前支持的构建配置:
| 操作系统 | 架构 | 构建类型 | 说明 |
|---------|------|---------|------|
| Ubuntu Latest | x86_64 | Release | 主要构建配置 |
| Ubuntu Latest | x86_64 | Debug | 调试构建配置 |
| Ubuntu Latest | aarch64 | Release | ARM64 架构构建(使用 QEMU 模拟) |
### 架构支持说明
- **x86_64**: 原生支持,使用系统默认工具链
- **aarch64**: 使用交叉编译工具链(`aarch64-linux-gnu-gcc`)和 QEMU 用户模式模拟运行测试
- 构建速度较慢(由于 QEMU 模拟)
- 测试并行度降低(使用 2 个并行任务)
- 确保库文件在 `lib/aarch64/` 目录下可用
## 作业说明
### 1. build-and-test
**目的**: 构建项目并运行所有单元测试
**步骤**:
1. 检出代码
2. 设置 QEMU(仅 aarch64 架构)
3. 缓存 CMake 构建文件
4. 安装构建依赖(CMake、GCC、Git 等,aarch64 需要交叉编译工具链)
5. 验证工具链版本
6. 配置 CMake(启用测试,aarch64 使用交叉编译配置)
7. 构建项目
8. 运行测试(使用 CTest,aarch64 通过 QEMU 运行)
9. 显示测试结果和构建产物
**架构特定说明**:
- **x86_64**: 使用系统原生工具链,并行运行所有测试
- **aarch64**: 使用 `aarch64-linux-gnu-gcc` 交叉编译,通过 QEMU 用户模式运行测试(并行度降低为 2)
**输出**: 测试结果、构建的可执行文件信息
### 2. build-examples
**目的**: 验证示例程序可以成功构建
**步骤**:
1. 检出代码
2. 安装构建依赖
3. 配置 CMake(禁用测试)
4. 构建示例程序(`toolset_example``action_group_show_l10`
5. 验证构建产物
**依赖**: 等待 `build-and-test` 作业完成
**输出**: 示例程序构建状态
### 3. code-quality
**目的**: 执行代码质量检查
**检查项**:
- 文件编码和换行符(确保使用 LF)
- CMake 语法验证
- 头文件包含检查
**输出**: 代码质量报告
### 4. summary
**目的**: 生成 CI/CD 摘要报告
**功能**: 汇总所有作业的状态,生成易于阅读的摘要
## 状态徽章
将以下 Markdown 代码添加到 README.md 以显示构建状态:
```markdown
![CI/CD](https://github.com/你的用户名/linkerhand-cpp-sdk/workflows/CI/CD/badge.svg)
```
或者使用 shields.io 样式:
```markdown
![CI/CD Status](https://img.shields.io/github/workflow/status/你的用户名/linkerhand-cpp-sdk/CI/CD?label=CI%2FCD)
```
## 故障排查
### 常见问题
#### 1. 测试失败
**症状**: `build-and-test` 作业失败
**可能原因**:
- 代码更改导致测试失败
- 测试超时
- 依赖库未找到
**解决方法**:
1. 查看作业日志中的详细错误信息
2. 在本地运行测试:`cd build && ctest --output-on-failure`
3. 检查 CMake 配置是否正确
#### 2. 构建失败
**症状**: CMake 配置或构建步骤失败
**可能原因**:
- CMakeLists.txt 语法错误
- 缺少依赖库
- 编译器版本不兼容
**解决方法**:
1. 检查 CMake 输出日志
2. 验证本地构建是否成功
3. 检查依赖库路径配置
#### 3. 缓存问题
**症状**: 构建使用过期的缓存
**解决方法**:
1. 在 GitHub Actions 界面清除缓存
2. 修改工作流中的缓存 key
3. 手动触发工作流
#### 4. aarch64 构建问题
**症状**: aarch64 架构构建或测试失败
**可能原因**:
- QEMU 未正确设置
- 交叉编译工具链未安装
- 库文件路径不正确
- QEMU 模拟运行失败
**解决方法**:
1. 检查 QEMU 设置步骤是否成功
2. 验证交叉编译工具链是否安装:`aarch64-linux-gnu-gcc --version`
3. 确认 `lib/aarch64/` 目录下存在库文件
4. 检查测试可执行文件架构:`file build/test_*`
5. 查看 QEMU 相关日志和错误信息
6. 如果 QEMU 模拟失败,可以尝试减少并行任务数量
### 查看日志
1. 进入 GitHub 仓库
2. 点击 "Actions" 标签
3. 选择失败的工作流运行
4. 查看具体作业的日志
## 本地测试
在提交代码前,建议在本地运行 CI/CD 流程:
### 1. 安装依赖
```bash
sudo apt-get update
sudo apt-get install -y \
build-essential \
cmake \
git \
libpthread-stubs0-dev
```
### 2. 运行完整构建和测试
#### x86_64 架构
```bash
# 创建构建目录
mkdir -p build
cd build
# 配置 CMake(启用测试,CMake 最低版本要求 3.15)
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON
# 构建项目
cmake --build . --config Release --parallel $(nproc)
# 运行测试
ctest --output-on-failure --parallel $(nproc)
```
#### aarch64 架构(交叉编译)
```bash
# 安装交叉编译工具链和 QEMU
sudo apt-get update
sudo apt-get install -y \
gcc-aarch64-linux-gnu \
g++-aarch64-linux-gnu \
qemu-user-static \
binfmt-support
# 创建构建目录
mkdir -p build
cd build
# 配置 CMake(交叉编译,CMake 最低版本要求 3.15)
cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_TESTING=ON \
-DCMAKE_SYSTEM_NAME=Linux \
-DCMAKE_SYSTEM_PROCESSOR=aarch64 \
-DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc \
-DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++ \
-DCMAKE_FIND_ROOT_PATH=/usr/aarch64-linux-gnu
# 构建项目
cmake --build . --config Release --parallel $(nproc)
# 运行测试(通过 QEMU)
export QEMU_LD_PREFIX=/usr/aarch64-linux-gnu
ctest --output-on-failure --parallel 2
```
### 3. 构建示例程序
```bash
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF
cmake --build . --target toolset_example action_group_show_l10
```
### 4. 代码质量检查
```bash
# 检查文件编码
find . -type f \( -name "*.cpp" -o -name "*.h" \) \
-exec file {} \; | grep -q "CRLF" && echo "发现 CRLF" || echo "✓ 使用 LF"
# 验证 CMake 语法
cmake --version
mkdir -p /tmp/cmake-check
cd /tmp/cmake-check
cmake /path/to/project -Wno-dev
```
## 最佳实践
1. **提交前测试**: 在本地运行测试确保通过
2. **小步提交**: 频繁提交小改动,便于定位问题
3. **查看日志**: 失败时仔细查看详细日志
4. **更新文档**: 修改 CI/CD 配置时更新本文档
5. **监控状态**: 定期检查构建状态,及时修复问题
## 相关文档
- [测试文档](tests/README.md) - 测试框架和运行指南
- [构建文档](../README.md#构建项目) - 项目构建说明
- [故障排查](TROUBLESHOOTING.md) - 常见问题解决方案
## 贡献
如果您想改进 CI/CD 配置:
1. Fork 仓库
2. 创建特性分支
3. 修改 `.github/workflows/ci.yml`
4. 测试您的更改
5. 提交 Pull Request
---
**最后更新**: 2026-01-XX
**注意**: 本文档描述了项目的 CI/CD 配置。如果您的项目尚未配置 CI/CD,可以参考本文档进行配置。实际的工作流配置文件位于 `.github/workflows/ci.yml`
This diff is collapsed.
This diff is collapsed.
# L6 灵巧手 API 开发指南
## 1. 指南目标
本文聚焦 `L6/O6` 型号在当前 SDK 中的 API 使用与二次开发方法,回答三个问题:
1. 应该使用哪一层接口
2. 哪些能力属于通用 API,哪些属于 L6 专有 API
3. 做二次封装时应如何避免常见误用
## 2. API 分层结构
L6 在当前仓库中的调用链如下:
```text
应用代码
-> LinkerHandApi
-> HandFactory
-> IHand
-> L6Hand
```
各层职责:
| 层级 | 主要文件 | 作用 |
|---|---|---|
| 业务入口层 | `include/LinkerHandApi.h` | 对外提供统一接口 |
| 实例工厂层 | `include/HandFactory.h` | 根据型号和通信方式实例化具体手型 |
| 抽象接口层 | `include/IHand.h` | 定义跨型号公共能力与异常行为 |
| 型号实现层 | `include/LinkerHandL6.h` | 暴露 L6/O6 专有接口 |
## 3. 推荐使用策略
### 3.1 优先使用 `LinkerHandApi`
适用场景:
- 快速接入 L6
- 做上层业务逻辑
- 多型号共用一套代码
- 仅使用基础动作与基础状态读取
典型接口:
- `fingerMove`
- `setSpeed`
- `setTorque`
- `getState`
- `getSpeed`
- `getTorque`
- `getTemperature`
- `getFaultCode`
- `getForce`
- `getVersion`
### 3.2 需要 L6 专有能力时直接使用 `L6Hand`
适用场景:
- 需要加速度控制
- 需要重调零
- 需要读取序列号/硬件版本/机械版本
- 需要恢复出厂设置或保存参数
## 4. L6 能力矩阵
### 4.1 通过 `LinkerHandApi` 可直接调用的 L6 能力
| 方法 | 说明 | L6 使用建议 |
|---|---|---|
| `fingerMove(const std::vector<uint8_t>&)` | 位置控制 | 数组长度固定为 `6` |
| `setSpeed(const std::vector<uint8_t>&)` | 速度设置 | 建议长度 `6` |
| `setTorque(const std::vector<uint8_t>&)` | 扭矩设置 | 建议长度 `6` |
| `getState()` | 当前位置 | 返回当前 6 关节状态 |
| `getSpeed()` | 当前速度 | 用于回读配置 |
| `getTorque()` | 当前扭矩 | 用于回读配置 |
| `getTemperature()` | 温度信息 | 适合做热保护监控 |
| `getFaultCode()` | 故障码 | 每轮任务前后都应检查 |
| `clearFaultCode()` | 清故障 | 对跨型号代码建议包装异常处理 |
| `getForce()` | 触觉/压力矩阵 | 返回三维向量 |
| `getVersion()` | 版本信息 | 用于联机确认 |
### 4.2 仅在 `L6Hand` 中暴露的能力
| 方法 | 说明 |
|---|---|
| `setAcceleration(const std::vector<uint8_t>&)` | 设置 6 关节加速度 |
| `getAcceleration()` | 读取加速度 |
| `reHome()` | 重新调零 |
| `getSerialNumber()` | 获取设备序列号 |
| `getHardwareVersion()` | 获取硬件版本 |
| `getMechanicalVersion()` | 获取机械结构版本 |
| `factoryReset()` | 恢复出厂设置 |
| `saveParameters()` | 保存参数 |
如果你的项目需要这些能力,不建议只停留在 `LinkerHandApi` 层。
## 5. 构造方式与通信选择
### 5.1 推荐构造方式
```cpp
LinkerHandApi hand(LINKER_HAND::L6, HAND_TYPE::RIGHT, COMM_CAN_0);
```
或者显式指定通道:
```cpp
LinkerHandApi hand(LINKER_HAND::L6, HAND_TYPE::RIGHT, "can0", 1000000);
```
### 5.2 工厂逻辑中的通信差异
`include/HandFactory.h` 当前实现可见:
- 枚举构造器 `LinkerHandApi(..., COMM_TYPE)` 会把
- `COMM_CAN_0` 映射为 `can0`
- `COMM_CAN_1` 映射为 `can1`
- `COMM_ETHERCAT` 映射为 `ethercat`
- 对 L6 来说,上述三者都会走到 `L6Hand` 创建逻辑
但字符串构造器 `LinkerHandApi(..., std::string canChannel, int baudrate)` 的工厂判断是:
- `canChannel` 包含 `"can"` 时才走 L6/L7/L10/L20/L25 的 CAN 分支
- `canChannel == "modbus"` 只支持 `L10`
这意味着:
- 对 L6,`"can0"``"can1"` 是明确可用路径
- 如果你想走 `COMM_ETHERCAT`,优先使用枚举构造器,不要假设字符串 `"ethercat"` 在当前重载中一定可用
这是当前头文件逻辑直接能确认的限制,二次开发时应显式规避。
## 6. 基础 API 示例
### 6.1 使用 `LinkerHandApi`
```cpp
#include "LinkerHandApi.h"
#include <chrono>
#include <thread>
int main() {
LinkerHandApi hand(LINKER_HAND::L6, HAND_TYPE::LEFT, COMM_CAN_0);
std::vector<uint8_t> speed(6, 180);
std::vector<uint8_t> torque(6, 220);
std::vector<uint8_t> open_pose = {255, 255, 255, 255, 255, 255};
std::vector<uint8_t> close_pose = {120, 160, 0, 0, 0, 0};
hand.setSpeed(speed);
hand.setTorque(torque);
hand.fingerMove(open_pose);
std::this_thread::sleep_for(std::chrono::milliseconds(600));
hand.fingerMove(close_pose);
std::this_thread::sleep_for(std::chrono::milliseconds(600));
auto state = hand.getState();
auto force = hand.getForce();
auto fault = hand.getFaultCode();
}
```
### 6.2 使用 `L6Hand`
```cpp
#include "LinkerHandL6.h"
#include <iostream>
int main() {
linkerhand::hand::L6Hand hand(static_cast<uint32_t>(HAND_TYPE::RIGHT), "can0", 1000000);
hand.setSpeed(std::vector<uint8_t>(6, 150));
hand.setTorque(std::vector<uint8_t>(6, 180));
hand.setAcceleration(std::vector<uint8_t>(6, 90));
std::cout << hand.getVersion() << std::endl;
std::cout << hand.getSerialNumber() << std::endl;
return 0;
}
```
## 7. `getForce()` 数据使用建议
`LinkerHandApi::getForce()` 返回:
```cpp
std::vector<std::vector<std::vector<uint8_t>>>
```
现有文档与示例中,通常把第一维按五指处理:
- `0`:拇指
- `1`:食指
- `2`:中指
- `3`:无名指
- `4`:小指
建议做法:
- 先打印原始结构,确认维度
- 再做语义命名和阈值封装
- 将触觉解析与动作控制拆开,避免强耦合
## 8. 异常与兼容性策略
### 8.1 未实现能力会抛异常
`IHand` 中大量默认方法会抛出 `UnsupportedFeatureException`,因此跨型号开发时建议统一做保护:
```cpp
try {
auto value = hand.getCurrent();
} catch (const linkerhand::UnsupportedFeatureException& e) {
std::cerr << e.what() << std::endl;
}
```
### 8.2 参数错误要在业务层先拦截
建议所有二次封装先检查:
- 位置数组长度是否为 `6`
- 速度/扭矩/加速度长度是否为 `6`
- 数值是否在 `0-255`
例如:
```cpp
void ensureL6Vector(const std::vector<uint8_t>& data) {
if (data.size() != 6) {
throw std::invalid_argument("L6 expects exactly 6 joint values");
}
}
```
## 9. 二次封装建议
### 9.1 业务层包装器
建议封装一个 `L6Controller`,只暴露业务语义接口,例如:
- `openHand()`
- `prepareGrip()`
- `pinch()`
- `release()`
- `readStatus()`
不要让上层任务代码直接散落大量 `std::vector<uint8_t>` 常量。
### 9.2 型号能力分层
如果项目同时支持 `L6/L7/L10/L20`,建议分成:
- 公共接口层:只调用 `LinkerHandApi`
- 型号扩展层:只在确有必要时下钻到 `L6Hand`
这样既能复用业务逻辑,也能保留 L6 的特性能力。
### 9.3 基于当前仓库的事实约束
当前仓库中很多实现位于预编译动态库中,因此在做深度扩展时要区分两类问题:
- 头文件已经声明,但库内部行为未公开
- 头文件本身就没有声明
前者可以通过小样例验证;后者则需要新增头文件接口与实现。
## 10. 推荐阅读顺序
1. `include/LinkerHandApi.h`
2. `include/HandFactory.h`
3. `include/IHand.h`
4. `include/LinkerHandL6.h`
5. `src/main.cpp`
6. `examples/toolset_example.cpp`
7. `docs/API-Reference.md`
8. `docs/TROUBLESHOOTING.md`
# L6 灵巧手应用开发手册
## 1. 适用范围
本文面向使用 `LinkerHand-CPP-SDK` 开发 `L6/O6` 灵巧手上位机应用的工程师,重点覆盖:
- 开发环境准备
- L6 关节与数据模型
- 典型控制流程
- 触觉、状态、故障数据读取
- 应用层开发建议
本文内容基于当前仓库中的公开头文件、示例程序和构建脚本整理,适合作为二次开发入口文档。
## 2. 项目内的 L6 开发入口
L6 的开发主要有两条路径:
| 路径 | 入口 | 适用场景 |
|---|---|---|
| 通用业务接口 | `LinkerHandApi` | 快速接入、动作控制、通用状态查询 |
| L6 型号专用接口 | `linkerhand::hand::L6Hand` | 需要重调零、加速度、设备信息等 L6 专有能力 |
关联文件:
- `include/LinkerHandApi.h`:统一 API 入口
- `include/HandFactory.h`:型号与通信工厂
- `include/IHand.h`:抽象接口与异常约束
- `include/LinkerHandL6.h`:L6/O6 专有能力定义
- `examples/toolset_example.cpp`:交互式示例
- `src/main.cpp`:最小示例
## 3. 开发环境准备
### 3.1 软件环境
- Linux
- `x86_64``aarch64`
- CMake `3.15+`
- GCC `7+` 或 Clang `5+`
- `pthread`
### 3.2 构建 SDK
```bash
mkdir build
cd build
cmake ..
make
```
也可以使用仓库脚本:
```bash
./script.sh
```
### 3.3 CAN 通信准备
L6 当前仓库中的常规接入方式以 CAN 为主。典型配置:
```bash
sudo modprobe can
sudo modprobe can_raw
sudo ip link set can0 type can bitrate 1000000
sudo ip link set can0 up
ip link show can0
```
如果设备接在 `can1`,对应替换接口名即可。
## 4. L6 运动与数据模型
### 4.1 关节顺序
`L6` 的位置控制数组长度固定为 `6`,关节顺序如下:
| 索引 | 含义 |
|---|---|
| `0` | 大拇指弯曲 |
| `1` | 大拇指横摆 |
| `2` | 食指弯曲 |
| `3` | 中指弯曲 |
| `4` | 无名指弯曲 |
| `5` | 小拇指弯曲 |
### 4.2 数值语义
- 位置范围:`0-255`
- 当前文档按仓库现有 API 说明采用如下语义:
- `0`:更接近完全弯曲
- `255`:更接近完全伸直
实际机械极限、零位、方向仍应以设备标定结果和设备手册为准。
### 4.3 L6 专有协议能力
`include/LinkerHandL6.h` 可见,L6 除基础控制外还定义了以下能力:
- 位置控制
- 速度设置
- 扭矩设置
- 加速度设置
- 重调零 `reHome()`
- 温度读取
- 故障码读取与清除
- 触觉矩阵读取
- 软件/硬件/机械版本读取
- 序列号读取
- 恢复出厂设置与参数保存
注意:这些能力并不都透出到 `LinkerHandApi`,部分需要直接使用 `L6Hand`
## 5. 最小应用开发流程
### 5.1 使用统一 API 快速接入
```cpp
#include "LinkerHandApi.h"
#include <chrono>
#include <iostream>
#include <thread>
int main() {
LinkerHandApi hand(LINKER_HAND::L6, HAND_TYPE::RIGHT, "can0", 1000000);
std::vector<uint8_t> open_pose = {255, 255, 255, 255, 255, 255};
std::vector<uint8_t> pinch_pose = {160, 180, 20, 255, 255, 255};
hand.setSpeed(std::vector<uint8_t>(6, 180));
hand.setTorque(std::vector<uint8_t>(6, 200));
hand.fingerMove(open_pose);
std::this_thread::sleep_for(std::chrono::milliseconds(800));
hand.fingerMove(pinch_pose);
std::this_thread::sleep_for(std::chrono::milliseconds(800));
auto state = hand.getState();
auto fault = hand.getFaultCode();
std::cout << "joint count: " << state.size() << std::endl;
std::cout << "fault count: " << fault.size() << std::endl;
return 0;
}
```
推荐顺序:
1. 初始化对象
2. 设置速度和扭矩
3. 下发目标位姿
4. 等待动作完成
5. 读取状态/故障/温度/触觉数据
### 5.2 使用 L6 专有类获取更完整能力
```cpp
#include "LinkerHandL6.h"
#include <iostream>
int main() {
linkerhand::hand::L6Hand hand(static_cast<uint32_t>(HAND_TYPE::RIGHT), "can0", 1000000);
hand.setSpeed(std::vector<uint8_t>(6, 150));
hand.setTorque(std::vector<uint8_t>(6, 180));
hand.setAcceleration(std::vector<uint8_t>(6, 100));
std::cout << "version: " << hand.getVersion() << std::endl;
std::cout << "serial: " << hand.getSerialNumber() << std::endl;
return 0;
}
```
当应用需要以下能力时,优先使用 `L6Hand`
- `setAcceleration`
- `getAcceleration`
- `reHome`
- `getSerialNumber`
- `getHardwareVersion`
- `getMechanicalVersion`
- `factoryReset`
- `saveParameters`
## 6. 典型应用模式
### 6.1 预设动作控制
适合演示、产线联调、抓取动作库调用。可直接参考 `examples/toolset_example.cpp` 中的 `L6_POSE_1`
建议做法:
- 将常用动作封装为固定长度的 `std::vector<uint8_t>(6)`
- 每个动作之间保留 `100-1000ms` 过渡时间
- 动作切换前检查故障码
### 6.2 状态轮询
适合闭环可视化、日志记录、简易监控。
推荐读取项:
- `getState()`
- `getTemperature()`
- `getFaultCode()`
- `getForce()`
轮询周期建议从 `50-200ms` 起步,根据总线和上位机实时性要求调整。
### 6.3 触觉驱动应用
L6 头文件中定义了拇指、食指、中指、无名指、小指以及掌部触觉相关帧。当前示例程序对 `getForce()` 的使用方式是按五指矩阵读取并打印。
适合场景:
- 抓取接触检测
- 滑移趋势判断
- 触碰事件触发
- 人机交互实验
开发建议:
- 先记录空载基线
- 用阈值做一层滤波与触发
- 不要直接把原始矩阵值作为动作切换唯一条件
### 6.4 故障恢复
推荐在每轮任务前后调用:
- `getFaultCode()`
- 必要时 `clearFaultCode()`
如果应用直接使用 `L6Hand`,可进一步封装:
- 故障码非零立即停止新动作下发
- 清故障后重新回到张开位
- 重复故障时提示人工检查通信、供电和机构
## 7. 接口使用约束
### 7.1 向量长度必须严格匹配
L6 常用数组长度均应按 `6` 个关节处理,包括:
- `fingerMove`
- `setSpeed`
- `setTorque`
- `setAcceleration`
不要把 L10/L20 的示例长度直接复用到 L6。
### 7.2 并非所有型号都支持所有接口
`IHand` 中大量方法默认会抛出 `UnsupportedFeatureException`。这意味着:
- 某个接口在 `LinkerHandApi` 中存在
- 不等于当前型号一定实现了该接口
对跨型号应用,建议统一加异常处理。
### 7.3 动作控制要保留缓冲时间
SDK 接口是命令式下发,不会自动等待动作完成。应用层需要自己控制:
- 动作间隔
- 超时
- 重试
- 状态回读
### 7.4 当前仓库中实现以预编译库为主
当前仓库公开了头文件与二进制库,很多实现细节位于 `lib/x86_64/``lib/aarch64/` 下的 `.so` 中。开发时应以:
- 头文件签名
- 示例代码
- 现有文档
作为第一依据。
## 8. 推荐的应用层封装方式
建议把应用分成三层:
1. 设备适配层:封装 `LinkerHandApi``L6Hand`
2. 动作层:定义张开、握持、捏取、释放等动作模板
3. 任务层:把动作与视觉、力控、机械臂流程连接
一个稳定的应用封装至少应包含:
- 初始化与连接检测
- 安全张开位
- 动作超时控制
- 故障查询与恢复
- 数据日志
## 9. 调试建议
建议先跑仓库自带示例,再做业务接入:
```bash
./script.sh
```
或:
```bash
mkdir build
cd build
cmake ..
make
./toolset_example
```
优先验证:
- CAN 接口是否正常
- `getVersion()` 是否有返回
- `fingerMove()` 是否能完成张开/闭合
- `getState()``getFaultCode()` 是否能持续读取
## 10. 相关文档
- `docs/API-Reference.md`
- `docs/TROUBLESHOOTING.md`
- `docs/FAQ.md`
- `examples/README.md`
This diff is collapsed.
# LinkerHand C++ SDK 命名规范改进指南
## 概述
本文档描述了 LinkerHand C++ SDK 的命名规范改进,这些改进旨在提高代码一致性、可维护性和可读性。
## 改进内容
### 1. 统一命名空间结构
所有手型号实现类已迁移到统一的 `linkerhand::hand` 命名空间:
**改进前:**
```cpp
namespace LinkerHandL10 {
class LinkerHand : public linkerhand::hand::IHand { ... };
}
```
**改进后:**
```cpp
namespace linkerhand {
namespace hand {
class L10Hand : public IHand { ... };
} // namespace hand
} // namespace linkerhand
```
### 2. 类名改进
不同型号的手现在使用不同的类名,便于区分:
| 型号 | 旧类名 | 新类名 | 命名空间 |
|------|--------|--------|---------|
| L6/O6 | `LinkerHandL6::LinkerHand` | `linkerhand::hand::L6Hand` | `linkerhand::hand` |
| L7 | `LinkerHandL7::LinkerHand` | `linkerhand::hand::L7Hand` | `linkerhand::hand` |
| L10 | `LinkerHandL10::LinkerHand` | `linkerhand::hand::L10Hand` | `linkerhand::hand` |
| L20 | `LinkerHandL20::LinkerHand` | `linkerhand::hand::L20Hand` | `linkerhand::hand` |
| L25/L21 | `LinkerHandL25::LinkerHand` | `linkerhand::hand::L25Hand` | `linkerhand::hand` |
| Modbus L10 | `ModbusLinkerHandL10::LinkerHand` | `linkerhand::hand::ModbusL10Hand` | `linkerhand::hand` |
### 3. 帧属性枚举重命名和改进
各型号的帧属性枚举已重命名,使用更清晰的命名,并改为使用 `enum class` 以避免命名冲突:
| 型号 | 旧枚举名 | 新枚举名 |
|------|---------|---------|
| L6/O6 | `LinkerHandL6::FRAME_PROPERTY` | `linkerhand::hand::L6FrameProperty` |
| L7 | `LinkerHandL7::FRAME_PROPERTY` | `linkerhand::hand::L7FrameProperty` |
| L10 | `LinkerHandL10::FRAME_PROPERTY` | `linkerhand::hand::L10FrameProperty` |
| L20 | `LinkerHandL20::FRAME_PROPERTY` | `linkerhand::hand::L20FrameProperty` |
| L25/L21 | `LinkerHandL25::FRAME_PROPERTY` | `linkerhand::hand::L25FrameProperty` |
**重要改进**
- 枚举类型从 `typedef enum` 改为 `enum class`(C++11 强类型枚举)
- 解决了多个枚举类型之间的命名冲突问题
- 枚举值现在需要使用作用域限定符访问(如 `L6FrameProperty::JOINT_POSITION`
**使用示例**
```cpp
// 旧方式(已废弃,但仍可通过向后兼容别名使用)
LinkerHandL6::FRAME_PROPERTY prop = LinkerHandL6::FRAME_PROPERTY::JOINT_POSITION;
// 新方式(推荐)
linkerhand::hand::L6FrameProperty prop = linkerhand::hand::L6FrameProperty::JOINT_POSITION;
// 转换为整数(用于 CAN 帧)
uint8_t frameProperty = static_cast<uint8_t>(linkerhand::hand::L6FrameProperty::JOINT_POSITION);
```
### 4. 参数命名修正
修正了错误的参数命名:
**改进前(错误)**
```cpp
LinkerHandApi(const LINKER_HAND &handJoint, ...);
LINKER_HAND handJoint_; // 这是手型号,不是关节
```
**改进后(正确)**
```cpp
LinkerHandApi(const LINKER_HAND &handModel, ...);
LINKER_HAND handModel_; // 手型号(L6, L7, L10, L20, L21, L25 等)
```
**说明**
- `handJoint` 命名不准确,因为这是手型号(Model),不是关节(Joint)
- 已统一修正为 `handModel`,更准确地表示手型号
## 向后兼容性
为了保持向后兼容,所有旧的命名空间和类名都通过 `using` 别名保留:
```cpp
// 向后兼容:在旧命名空间中提供别名
namespace LinkerHandL10 {
using FRAME_PROPERTY = linkerhand::hand::L10FrameProperty;
using LinkerHand = linkerhand::hand::L10Hand;
} // namespace LinkerHandL10
```
这意味着现有代码可以继续使用旧的命名,无需立即修改。
## 使用示例
### 使用新命名(推荐)
```cpp
#include "LinkerHandL10.h"
#include "HandFactory.h"
// 使用新的命名空间和类名
using namespace linkerhand;
auto hand = factory::HandFactory::createHand(
LINKER_HAND::L10,
HAND_TYPE::RIGHT,
COMM_CAN_0
);
// hand 的类型是 std::unique_ptr<hand::IHand>
// 实际类型是 hand::L10Hand
```
### 使用旧命名(向后兼容)
```cpp
#include "LinkerHandL10.h"
#include "HandFactory.h"
// 仍然可以使用旧的命名空间
LinkerHandL10::LinkerHand hand(0x27, "can0", 1000000);
```
## 迁移建议
### 阶段一:保持兼容(当前)
- ✅ 所有新命名已实现
- ✅ 向后兼容别名已添加
- ✅ 现有代码无需修改即可继续工作
### 阶段二:逐步迁移(推荐)
1. **新代码使用新命名**:所有新编写的代码应使用新的命名空间和类名
2. **更新文档和示例**:逐步更新文档和示例代码使用新命名
3. **代码审查**:在代码审查中鼓励使用新命名
### 阶段三:完全迁移(未来)
在未来版本中,可以考虑:
- 移除向后兼容别名(需要主版本号升级)
- 更新所有内部代码使用新命名
- 更新所有文档和示例
## 优势
1. **一致性**:所有型号使用统一的命名空间结构
2. **可读性**:类名直接反映型号(`L10Hand` vs `LinkerHand`
3. **可维护性**:统一的命名空间便于管理和查找
4. **扩展性**:新型号可以轻松添加到同一命名空间
## 注意事项
1. **工厂类**`HandFactory` 已更新为使用新类名,但返回类型仍然是 `IHand` 接口
2. **头文件**:头文件名暂时保持不变,未来可以考虑重命名
3. **枚举**
- 帧属性枚举已重命名,枚举值保持不变
- 枚举类型已改为 `enum class`,需要使用作用域限定符访问枚举值
- 这解决了多个枚举类型之间的命名冲突问题(如 `JOINT_POSITION``TORQUE_LIMIT` 等)
- 如果需要将枚举值转换为整数,使用 `static_cast<uint8_t>()`
## 相关文件
- `include/LinkerHandL6.h` - L6/O6 型号实现
- `include/LinkerHandL7.h` - L7 型号实现
- `include/LinkerHandL10.h` - L10 型号实现
- `include/LinkerHandL20.h` - L20 型号实现
- `include/LinkerHandL25.h` - L25/L21 型号实现
- `include/ModbusLinkerHandL10.h` - Modbus L10 型号实现
- `include/HandFactory.h` - 工厂类
## 版本信息
- **改进版本**:1.1.7+
- **兼容性**:完全向后兼容
This diff is collapsed.
# 示例代码说明
本文档介绍 LinkerHand-CPP-SDK 提供的示例代码及其使用方法。
## 目录
- [示例列表](#示例列表)
- [如何运行示例](#如何运行示例)
- [示例详细说明](#示例详细说明)
- [学习路径](#学习路径)
- [常见问题](#常见问题)
---
## 示例列表
| 示例文件 | 功能描述 | 适用型号 | 难度 |
|---------|---------|---------|------|
| `toolset_example.cpp` | 交互式工具集,演示所有 API 功能 | 所有型号 | 中级 |
| `o6_async_reader.cpp` | O6 后台采集示例,主线程只消费快照 | O6/L6 | 中级 |
| `action_group_show_l10.cpp` | L10 型号的动作组演示 | L10 | 初级 |
---
## 如何运行示例
### 方法一:使用安装脚本(推荐)
```bash
./script.sh
# 选择选项 [6]: Run examples
```
### 方法二:手动编译和运行
```bash
# 1. 构建项目
mkdir build && cd build
cmake ..
make
# 2. 运行示例
./toolset_example
./o6_async_reader
./action_group_show_l10
```
### 方法三:直接编译示例
```bash
# 编译 toolset_example
g++ -std=c++11 examples/toolset_example.cpp -I./include \
-L./lib/x86_64 -llinkerhand_cpp_sdk -lpthread -o toolset_example
# 编译 o6_async_reader
g++ -std=c++11 examples/o6_async_reader.cpp -I./include \
-L./lib/x86_64 -llinkerhand_cpp_sdk -lpthread -o o6_async_reader
# 运行
./toolset_example
./o6_async_reader
```
---
## 示例详细说明
### toolset_example.cpp
**功能描述**
交互式工具集,提供菜单界面,可以测试所有 API 功能。适合学习和调试。
**主要功能**:
1. 获取版本信息
2. 获取温度数据
3. 获取故障码
4. 获取电流数据
5. 获取扭矩数据
6. 获取速度数据
7. 获取压力传感信息
8. 循环获取关节状态
9. 执行预设动作
10. 清除故障码
**使用步骤**:
1. 运行程序
2. 选择机械手型号(L6/O6、L7、L10、L20、L21、L25)
3. 选择手部方向(左手/右手)
4. 选择通信接口(CAN0、CAN1、ModBus、EtherCAT)
5. 进入交互菜单,选择要测试的功能
**代码结构**:
```cpp
// 1. 选择型号
// 2. 选择手部方向
// 3. 选择通信接口
// 4. 创建 API 实例
LinkerHandApi hand(linkerhand, handType, channel);
// 5. 进入交互模式
interactiveMode(hand);
```
**预设动作**:
- **L6**: 单指循环弯曲
- **L7**: 握拳和张开
- **L10**:
- 手指循环弯曲
- 拇指与其他手指循环对指
- 手指侧摆
- 握拳和张开
- **L20**: 握拳和张开
- **L21/L25**: 握拳和张开
**使用示例**:
```bash
$ ./toolset_example
=========================
Example
=========================
Run Choose LinkerHand:
-------------------------
[1]: L6/O6
-------------------------
[2]: L7
-------------------------
[3]: L10
-------------------------
...
```
---
### o6_async_reader.cpp
**功能描述**
O6 后台采集示例。设备读取放到独立线程中执行,主线程只读取最近一次快照。
**主要功能**:
1. 周期性读取 `getState()`
2. 周期性读取 `getTorque()`
3. 周期性读取 `getTemperature()`
4. 周期性读取 `getFaultCode()`
5. 周期性读取 `getForce()`
6. 检测快照是否超时并标记 `STALE`
**适用场景**:
- 不希望采集逻辑阻塞主线程
- 需要持续监控 O6/L6 状态
- 希望把控制线程和读取线程拆开
**使用说明**:
- 默认参数为 `O6 + RIGHT + can0 + 1000000`
- 若项目还有控制线程,建议控制和读取分别使用独立 `LinkerHandApi` 实例
- 若读取长时间没有更新,输出会标记为 `STALE`
**使用示例**:
```bash
$ ./o6_async_reader
O6 async reader started.
Main thread only consumes cached snapshots.
Press Ctrl+C to stop.
```
---
### action_group_show_l10.cpp
**功能描述**
专门为 L10 型号设计的动作组演示程序。展示如何实现连续的动作序列。
**主要功能**:
- 演示 L10 型号的各种动作组合
- 展示动作序列的编程方式
- 适合学习动作控制的基本方法
**适用型号**: L10(适配球形拇指根部关节版本)
**代码特点**:
- 使用关节映射表管理关节状态
- 实现动作序列的循环执行
- 支持左右手(默认右手,修改 `HAND_TYPE::RIGHT``HAND_TYPE::LEFT` 可切换为左手)
**关节映射**:
```cpp
// L10 关节顺序
vector<string> joint_order = {
"joint1", // 拇指根部弯曲
"joint2", // 拇指侧摆
"joint3", // 食指根部弯曲
"joint4", // 中指根部弯曲
"joint5", // 无名指根部弯曲
"joint6", // 小指根部弯曲
"joint7", // 食指侧摆
"joint8", // 中指侧摆
"joint9", // 无名指侧摆
"joint10" // 拇指旋转
};
```
**使用示例**:
```bash
$ ./action_group_show_l10
# 程序会自动执行预设的动作序列
```
**注意事项**:
- 本示例仅支持 L10 型号
- 默认使用右手,如需左手请修改代码中的 `HAND_TYPE::RIGHT``HAND_TYPE::LEFT`
- 确保设备已正确连接
- 确保通信接口已正确配置(CAN 总线、ModBus 或 EtherCAT)
---
## 学习路径
### 初学者
1. **第一步**: 阅读 [README.md](../README.md) 了解基本概念
2. **第二步**: 运行 `action_group_show_l10.cpp` 查看基本动作
3. **第三步**: 阅读代码,理解关节控制的基本方法
4. **第四步**: 尝试修改代码,实现自己的动作
### 中级用户
1. **第一步**: 运行 `toolset_example.cpp` 熟悉所有 API
2. **第二步**: 阅读 [API 参考文档](../docs/API-Reference.md) 了解详细接口
3. **第三步**: 参考示例代码实现自己的应用
4. **第四步**: 学习传感器数据读取和处理
### 高级用户
1. **第一步**: 深入研究示例代码的实现细节
2. **第二步**: 优化代码性能
3. **第三步**: 实现复杂的动作序列
4. **第四步**: 集成到自己的项目中
---
## 代码学习要点
### 1. 初始化 API
```cpp
// 创建 API 实例
LinkerHandApi hand(LINKER_HAND::L10, HAND_TYPE::RIGHT);
```
### 2. 设置速度和扭矩
```cpp
// 设置速度(5 个元素对应 5 根手指)
std::vector<uint8_t> speed = {200, 200, 200, 200, 200};
hand.setSpeed(speed);
// 设置扭矩
std::vector<uint8_t> torque = {255, 255, 255, 255, 255};
hand.setTorque(torque);
```
### 3. 控制关节运动
```cpp
// L10 需要 10 个关节值
std::vector<uint8_t> pose = {128, 128, 128, 128, 128, 128, 128, 128, 128, 128};
hand.fingerMove(pose);
// 等待动作完成
std::this_thread::sleep_for(std::chrono::seconds(1));
```
### 4. 读取传感器数据
```cpp
// 获取压力数据
auto force = hand.getForce();
// 获取关节状态
auto state = hand.getState();
// 获取温度
auto temperature = hand.getTemperature();
```
### 5. 错误处理
```cpp
// 检查故障码
auto fault_code = hand.getFaultCode();
for (size_t i = 0; i < fault_code.size(); i++) {
if (fault_code[i] != 0) {
std::cout << "警告: 电机 " << i << " 故障码: "
<< static_cast<int>(fault_code[i]) << std::endl;
}
}
```
---
## 常见问题
### Q1: 示例程序无法运行?
**A**: 检查以下几点:
1. 确保已正确编译
2. 检查库文件路径是否正确
3. 确保设备已连接
4. 检查通信接口配置(CAN 总线等)
详细排查请参考 [故障排查指南](../docs/TROUBLESHOOTING.md)
---
### Q2: 如何修改示例代码适配其他型号?
**A**:
1. 修改型号参数:
```cpp
// 从 L10 改为 L20
LinkerHandApi hand(LINKER_HAND::L20, HAND_TYPE::RIGHT);
```
2. 修改关节数组长度:
```cpp
// L20 需要 20 个关节值
std::vector<uint8_t> pose(20, 128);
```
3. 参考 [关节映射表](../README.md#关节映射表) 了解不同型号的关节配置
---
### Q3: 如何实现自己的动作序列?
**A**:
1. 定义动作数组:
```cpp
std::vector<std::vector<uint8_t>> actions = {
{255, 104, 255, 255, 255, 255, 255, 255, 255, 71}, // 动作1
{101, 60, 0, 0, 0, 0, 255, 255, 255, 51}, // 动作2
// ...
};
```
2. 循环执行:
```cpp
for (const auto& pose : actions) {
hand.fingerMove(pose);
std::this_thread::sleep_for(std::chrono::seconds(1));
}
```
---
### Q4: 如何读取和处理传感器数据?
**A**: 参考 `toolset_example.cpp` 中的示例:
```cpp
// 获取压力数据
auto force_data = hand.getForce();
// 遍历每根手指
for (size_t finger = 0; finger < force_data.size(); finger++) {
// 处理数据...
}
```
详细说明请参考 [API 参考文档](../docs/API-Reference.md#getforce)。
---
### Q5: 示例代码中的延时时间如何设置?
**A**: 延时时间取决于:
- 动作的复杂度
- 设置的速度
- 设备的响应时间
**建议**:
- 简单动作: 0.5-1 秒
- 复杂动作: 1-2 秒
- 连续动作: 根据实际效果调整
```cpp
// 快速动作
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// 标准动作
std::this_thread::sleep_for(std::chrono::seconds(1));
// 慢速动作
std::this_thread::sleep_for(std::chrono::seconds(2));
```
---
## 下一步
- 阅读 [API 参考文档](../docs/API-Reference.md) 了解完整的 API
- 查看 [故障排查指南](../docs/TROUBLESHOOTING.md) 解决遇到的问题
- 参考 [常见问题解答](../docs/FAQ.md) 获取更多帮助
- 贡献您的示例代码到项目
如有问题,请参考 [故障排查指南](../docs/TROUBLESHOOTING.md) 或联系技术支持。
This diff is collapsed.
#include "LinkerHandApi.h"
#include "ErrorCode.h"
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <exception>
#include <iomanip>
#include <iostream>
#include <mutex>
#include <sstream>
#include <string>
#include <thread>
#include <vector>
namespace {
using ForceData = std::vector<std::vector<std::vector<uint8_t>>>;
using Clock = std::chrono::steady_clock;
constexpr std::chrono::milliseconds kPollInterval(100);
constexpr std::chrono::milliseconds kPrintInterval(200);
constexpr std::chrono::milliseconds kStaleAfter(600);
struct O6Snapshot {
uint64_t sequence = 0;
bool ready = false;
Clock::time_point updated_at{};
std::vector<uint8_t> state;
std::vector<uint8_t> torque;
std::vector<uint8_t> temperature;
std::vector<uint8_t> fault_code;
ForceData force;
std::string last_error;
};
std::string bytesToString(const std::vector<uint8_t> &values) {
if (values.empty()) {
return "[]";
}
std::ostringstream oss;
oss << "[";
for (size_t i = 0; i < values.size(); ++i) {
if (i != 0) {
oss << ' ';
}
oss << static_cast<int>(values[i]);
}
oss << "]";
return oss.str();
}
void appendError(O6Snapshot &snapshot, const std::string &label, const std::string &message) {
if (!snapshot.last_error.empty()) {
snapshot.last_error += " | ";
}
snapshot.last_error += label + ": " + message;
}
uint8_t maxSensorValue(const ForceData &force_data, size_t finger_index, size_t sensor_type_index) {
if (finger_index >= force_data.size()) {
return 0;
}
if (sensor_type_index >= force_data[finger_index].size()) {
return 0;
}
const auto &sensor_values = force_data[finger_index][sensor_type_index];
if (sensor_values.empty()) {
return 0;
}
return *std::max_element(sensor_values.begin(), sensor_values.end());
}
class O6AsyncReader {
public:
O6AsyncReader(HAND_TYPE hand_type,
std::string can_channel,
int baudrate,
std::chrono::milliseconds poll_interval)
: hand_(LINKER_HAND::O6, hand_type, can_channel, baudrate),
poll_interval_(poll_interval) {}
~O6AsyncReader() {
stop();
}
void start() {
if (running_.exchange(true)) {
return;
}
worker_ = std::thread(&O6AsyncReader::pollLoop, this);
}
void stop() {
if (!running_.exchange(false)) {
return;
}
if (worker_.joinable()) {
worker_.join();
}
}
O6Snapshot snapshot() const {
std::lock_guard<std::mutex> lock(snapshot_mutex_);
return snapshot_;
}
private:
template <typename Fn>
void readField(O6Snapshot &next, const char *name, Fn &&fn) {
try {
fn();
} catch (const linkerhand::UnsupportedFeatureException &e) {
appendError(next, name, std::string("unsupported - ") + e.what());
} catch (const std::exception &e) {
appendError(next, name, e.what());
}
}
void pollLoop() {
uint64_t sequence = 0;
while (running_.load()) {
const auto cycle_started = Clock::now();
O6Snapshot next;
next.sequence = ++sequence;
next.ready = true;
next.updated_at = cycle_started;
readField(next, "getState", [&]() { next.state = hand_.getState(); });
readField(next, "getTorque", [&]() { next.torque = hand_.getTorque(); });
readField(next, "getTemperature", [&]() { next.temperature = hand_.getTemperature(); });
readField(next, "getFaultCode", [&]() { next.fault_code = hand_.getFaultCode(); });
readField(next, "getForce", [&]() { next.force = hand_.getForce(); });
{
std::lock_guard<std::mutex> lock(snapshot_mutex_);
snapshot_ = std::move(next);
}
std::this_thread::sleep_until(cycle_started + poll_interval_);
}
}
LinkerHandApi hand_;
std::chrono::milliseconds poll_interval_;
std::atomic<bool> running_{false};
std::thread worker_;
mutable std::mutex snapshot_mutex_;
O6Snapshot snapshot_;
};
void printSnapshot(const O6Snapshot &snapshot) {
if (!snapshot.ready) {
std::cout << "waiting for first O6 snapshot..." << std::endl;
return;
}
const auto age = std::chrono::duration_cast<std::chrono::milliseconds>(
Clock::now() - snapshot.updated_at);
const bool stale = age > kStaleAfter;
const uint8_t thumb_force = maxSensorValue(snapshot.force, 0, 0);
const uint8_t index_force = maxSensorValue(snapshot.force, 1, 0);
std::cout << "seq=" << snapshot.sequence
<< " age=" << age.count() << "ms";
if (stale) {
std::cout << " STALE";
}
std::cout << '\n';
std::cout << "state : " << bytesToString(snapshot.state) << '\n';
std::cout << "torque : " << bytesToString(snapshot.torque) << '\n';
std::cout << "temperature : " << bytesToString(snapshot.temperature) << '\n';
std::cout << "fault : " << bytesToString(snapshot.fault_code) << '\n';
std::cout << "thumb_force : " << static_cast<int>(thumb_force)
<< ", index_force: " << static_cast<int>(index_force) << '\n';
if (!snapshot.last_error.empty()) {
std::cout << "last_error : " << snapshot.last_error << '\n';
}
}
} // namespace
int main() {
try {
// 读取使用独立 SDK 实例和独立线程。
// 如果你的控制线程因为 fingerMove 等调用卡住,主线程和这个读取线程不会一起被抢占。
// 若你还需要控制,请为控制路径再创建一个独立 LinkerHandApi 实例,不要和读取复用同一个对象。
O6AsyncReader reader(HAND_TYPE::RIGHT, "can0", 1000000, kPollInterval);
reader.start();
std::cout << "O6 async reader started." << std::endl;
std::cout << "Main thread only consumes cached snapshots." << std::endl;
std::cout << "Press Ctrl+C to stop." << std::endl;
while (true) {
const O6Snapshot snapshot = reader.snapshot();
printSnapshot(snapshot);
std::cout << "------------------------------" << std::endl;
std::this_thread::sleep_for(kPrintInterval);
}
} catch (const std::exception &e) {
std::cerr << "Failed to start O6 async reader: " << e.what() << std::endl;
return 1;
}
}
This diff is collapsed.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
#ifdef __linux__
#ifndef LINKERHAND_CAN_BUS_H
#define LINKERHAND_CAN_BUS_H
#include <iostream>
#include <iomanip>
#include <sstream>
#include <unistd.h>
#include <chrono>
#include <thread>
#include <queue>
#include <fcntl.h>
#include <mutex>
#include <atomic>
#include <cstring>
#include <cerrno>
#include "Common.h"
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h> // 包含 ifreq 和 IFNAMSIZ
#include <sys/ioctl.h> // 包含 ioctl
#include "ICanBus.h"
namespace linkerhand {
namespace communication {
/**
* @brief Linux CAN 总线实现
*/
class CanBus : public linkerhand::communication::ICanBus
{
public:
CanBus(const std::string& interface, int bitrate, const LINKER_HAND linkerhand);
~CanBus();
void send(const std::vector<uint8_t>& data, uint32_t can_id, const bool wait = false) override;
CANFrame recv(uint32_t& id) override;
void setReceiveTimeout(int seconds, int microseconds);
void updateSendRate();
void updateReceiveRate();
std::string printMillisecondTime();
private:
int socket_fd;
std::string interface;
int bitrate;
struct sockaddr_can addr;
struct ifreq ifr; // 确保 ifreq 是完整的类型
std::mutex mutex_send; // 互斥锁
std::mutex mutex_send2; // 互斥锁
std::mutex mutex_receive; // 互斥锁
std::mutex send_mutex; // 用于保护发送计数器和时间
std::atomic<int> send_count; // 发送计数器
std::chrono::steady_clock::time_point last_time; // 上一次记录时间
std::mutex receive_mutex; // 用于保护发送计数器和时间
std::atomic<int> receive_count; // 发送计数器
std::chrono::steady_clock::time_point receive_last_time; // 上一次记录时间
std::queue<struct can_frame> send_queue; // 发送队列
LINKER_HAND linker_hand;
};
} // namespace communication
} // namespace linkerhand
// 向后兼容:在全局命名空间中提供别名
namespace Communication = linkerhand::communication;
#endif
#endif // LINKERHAND_CAN_BUS_H
/*
* @Author: liangshaoteng liangshaoteng2012@163.com
* @Date: 2026-01-29 17:01:44
* @LastEditors: liangshaoteng liangshaoteng2012@163.com
* @LastEditTime: 2026-03-09 13:19:16
* @FilePath: /linkerhand/include/CanBusFactory.h
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
#ifndef LINKERHAND_CAN_BUS_FACTORY_H
#define LINKERHAND_CAN_BUS_FACTORY_H
#include "ICanBus.h"
#include "CanBus.h"
#include "PCANBus.h"
#if USE_ETHERCAT
#include "EtherCAT.h"
#endif
#include <stdexcept>
namespace linkerhand {
namespace communication {
class CanBusFactory
{
public:
static std::unique_ptr<ICanBus> createCanBus(
uint32_t handId,
const std::string& interfaceOrChannel,
int bitrate,
const LINKER_HAND linkerHand
)
{
(void)handId; // 标记为未使用,避免警告
#ifdef _WIN32
// Windows 平台
TPCANHandle channel = PCAN_USBBUS1;
TPCANBaudrate baudrate = PCAN_BAUD_1M;
if (interfaceOrChannel == "can1") {
channel = PCAN_USBBUS2;
}
return std::make_unique<linkerhand::communication::PCANBus>(channel, baudrate, linkerHand);
#else
return std::make_unique<linkerhand::communication::CanBus>(interfaceOrChannel, bitrate, linkerHand);
#if USE_ETHERCAT
else if (interfaceOrChannel == "ethercat") {
return std::make_unique<linkerhand::communication::EtherCAT>(handId);
}
#endif
// 默认情况:抛出异常或返回 nullptr
throw std::runtime_error("Unsupported CAN interface: " + interfaceOrChannel);
// 或者返回 nullptr:
// return nullptr;
#endif
}
};
} // namespace communication
} // namespace linkerhand
// 向后兼容:在全局命名空间中提供别名
namespace Communication = linkerhand::communication;
#endif // LINKERHAND_CAN_BUS_FACTORY_H
#ifndef LINKERHAND_CAN_FRAME_H
#define LINKERHAND_CAN_FRAME_H
#include <iostream>
#include <cstdint>
#include <vector>
#ifdef _WIN32
#include "PCANBasic.h"
using CanFrame = TPCANMsg;
#elif defined(__linux__)
#include <linux/can.h>
#include <linux/can/raw.h>
using CanFrame = struct can_frame;
#elif defined(__APPLE__) && defined(__MACH__)
std::cout << "This is macOS!" << std::endl;
#else
#error "Unsupported platform"
#endif
// 公共的 CAN 帧结构体
struct CANFrame
{
uint32_t can_id; // CAN 帧 ID
uint8_t can_dlc; // 数据长度
uint8_t data[8]; // 数据
};
#endif // LINKERHAND_CAN_FRAME_H
\ No newline at end of file
#ifndef LINKERHAND_COMMON_H
#define LINKERHAND_COMMON_H
// 为了与预编译库兼容,保持旧的枚举定义(非枚举类)
enum LINKER_HAND {
O6,
L6,
L7,
L10,
L20,
L21,
L25
};
enum HAND_TYPE {
LEFT = 0x28,
RIGHT = 0x27
};
enum COMM_TYPE {
COMM_CAN_0,
COMM_CAN_1,
COMM_MODBUS,
COMM_ETHERCAT
};
namespace linkerhand {
/**
* @brief 通用配置和版本信息
*/
namespace Common {
inline float current_hand_version = 1.0;
}
/**
* @brief 新的枚举类(用于未来版本,当前保持向后兼容)
*
* 注意:当前库使用旧的枚举类型,这些枚举类用于未来版本
*/
enum class HandModel {
O6,
L6,
L7,
L10,
L20,
L21,
L25
};
enum class HandType {
LEFT = 0x28,
RIGHT = 0x27
};
enum class CommType {
CAN_0,
CAN_1,
MODBUS,
ETHERCAT
};
} // namespace linkerhand
#define SEND_DEBUG 0
#define RECV_DEBUG 0
#endif // LINKERHAND_COMMON_H
#ifndef LINKERHAND_ERROR_CODE_H
#define LINKERHAND_ERROR_CODE_H
#include <system_error>
#include <string>
namespace linkerhand {
/**
* @brief 错误码枚举
*
* 定义 LinkerHand SDK 的所有错误类型
*/
enum class HandError {
Success = 0, ///< 成功
UnsupportedFeature, ///< 不支持的功能
CommunicationError, ///< 通信错误
InvalidParameter, ///< 无效参数
DeviceNotFound, ///< 设备未找到
Timeout, ///< 超时
InvalidState, ///< 无效状态
OperationFailed, ///< 操作失败
NotInitialized, ///< 未初始化
AlreadyInitialized, ///< 已初始化
OutOfRange, ///< 超出范围
UnknownError ///< 未知错误
};
/**
* @brief 错误类别
*
* 实现 std::error_category 接口,用于错误码分类
*/
class HandErrorCategory : public std::error_category {
public:
const char* name() const noexcept override {
return "LinkerHand";
}
std::string message(int ev) const override {
switch (static_cast<HandError>(ev)) {
case HandError::Success:
return "Success";
case HandError::UnsupportedFeature:
return "Unsupported feature";
case HandError::CommunicationError:
return "Communication error";
case HandError::InvalidParameter:
return "Invalid parameter";
case HandError::DeviceNotFound:
return "Device not found";
case HandError::Timeout:
return "Timeout";
case HandError::InvalidState:
return "Invalid state";
case HandError::OperationFailed:
return "Operation failed";
case HandError::NotInitialized:
return "Not initialized";
case HandError::AlreadyInitialized:
return "Already initialized";
case HandError::OutOfRange:
return "Out of range";
case HandError::UnknownError:
default:
return "Unknown error";
}
}
};
/**
* @brief 获取错误类别实例
*/
inline const HandErrorCategory& hand_error_category() {
static HandErrorCategory instance;
return instance;
}
/**
* @brief 将 HandError 转换为 std::error_code
*/
inline std::error_code make_error_code(HandError e) {
return std::error_code(static_cast<int>(e), hand_error_category());
}
/**
* @brief 异常类:LinkerHand 异常基类
*/
class HandException : public std::runtime_error {
public:
HandException(HandError error, const std::string& message)
: std::runtime_error(message), error_code_(error) {}
HandError error_code() const noexcept {
return error_code_;
}
private:
HandError error_code_;
};
/**
* @brief 异常类:不支持的功能
*/
class UnsupportedFeatureException : public HandException {
public:
explicit UnsupportedFeatureException(const std::string& feature_name)
: HandException(HandError::UnsupportedFeature,
"Unsupported feature: " + feature_name),
feature_name_(feature_name) {}
const std::string& feature_name() const noexcept {
return feature_name_;
}
private:
std::string feature_name_;
};
/**
* @brief 异常类:通信错误
*/
class CommunicationException : public HandException {
public:
explicit CommunicationException(const std::string& message)
: HandException(HandError::CommunicationError, message) {}
};
/**
* @brief 异常类:无效参数
*/
class InvalidParameterException : public HandException {
public:
explicit InvalidParameterException(const std::string& message)
: HandException(HandError::InvalidParameter, message) {}
};
} // namespace linkerhand
// 注册错误码到标准库
namespace std {
template<>
struct is_error_code_enum<linkerhand::HandError> : true_type {};
}
#endif // LINKERHAND_ERROR_CODE_H
#ifdef __linux__
#ifndef LINKERHAND_ETHERCAT_H
#define LINKERHAND_ETHERCAT_H
#include <iostream>
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <atomic>
#include <chrono>
#include <vector>
#include <thread>
#include <signal.h>
#include <mutex>
#include <ecrt.h>
#include "Common.h"
#include "ICanBus.h"
// constexpr uint32_t PERIOD_US = 10000; // 10ms
constexpr uint32_t PERIOD_US = 1000; // 10ms
/* ------------- EtherCAT 常量 ------------- */
constexpr unsigned int MASTER_INDEX = 0;
constexpr uint32_t cycle_ns = 8000000; // 8ms 周期
namespace linkerhand {
namespace communication {
/**
* @brief EtherCAT 通信实现
*/
class EtherCAT : public ICanBus {
public:
EtherCAT(const uint32_t handId);
~EtherCAT();
bool init();
void start();
void stop();
void send_can_data(const unsigned int id, const std::vector<uint8_t> &data);
void send(const std::vector<uint8_t>& data, uint32_t can_id, const bool wait = false) override;
CANFrame recv(uint32_t& id) override;
private:
void configure_dc();
void register_pdo_entries();
void run_cycle();
void ec_write(const uint32_t can_id, const std::vector<uint8_t> &data);
/* ------------- PDO 条目 ------------- */
ec_pdo_entry_info_t slave_pdo_entries[12] = {
// 主站 → 从站(RxPDO)
{0x7000, 0x01, 16}, // Control_cmd
{0x7000, 0x02, 32}, // Position_Target_1
{0x7000, 0x03, 32}, // Velocity_Target_1
{0x7000, 0x04, 32}, // Torque_Target_1
{0x7000, 0x05, 32}, // KP_1
{0x7000, 0x06, 32}, // KD_1
// 从站 → 主站(TxPDO)
{0x6000, 0x01, 16}, // MOTOR_NUM
{0x6000, 0x02, 16}, // MOTOR_ID_1
{0x6000, 0x03, 16}, // State_1
{0x6000, 0x04, 32}, // Position_Actual_1
{0x6000, 0x05, 32}, // Velocity_Actual_1
{0x6000, 0x06, 32}, // Torque_Actual_1
};
ec_pdo_info_t slave_pdos[2] = {
{0x1600, 6, slave_pdo_entries + 0},
{0x1A00, 6, slave_pdo_entries + 6},
};
ec_sync_info_t slave_syncs[5] = {
{0, EC_DIR_OUTPUT, 0, NULL, EC_WD_DISABLE},
{1, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
{2, EC_DIR_OUTPUT, 1, slave_pdos + 0, EC_WD_ENABLE},
{3, EC_DIR_INPUT, 1, slave_pdos + 1, EC_WD_DISABLE},
{0xff}};
/* ------------- PDO 偏移量 ------------- */
unsigned int offset_control_cmd = 0;
unsigned int offset_position_target = 0;
unsigned int offset_velocity_target = 0;
unsigned int offset_torque_target = 0;
unsigned int offset_kp = 0;
unsigned int offset_kd = 0;
unsigned int offset_motor_num = 0;
unsigned int offset_motor_id = 0;
unsigned int offset_state = 0;
unsigned int offset_position_actual = 0;
unsigned int offset_velocity_actual = 0;
unsigned int offset_torque_actual = 0;
/* ------------- EtherCAT 句柄 ------------- */
ec_master_t *master = nullptr;
ec_domain_t *domain = nullptr;
uint8_t *domain_pd = nullptr;
bool running = true;
uint16_t cmd = 0x04;
uint32_t can_id_;
std::vector<uint8_t> can_data;
std::mutex mutex_send; // 互斥锁
uint16_t slave_alias;
uint16_t slave_position;
uint32_t vendor_id;
uint32_t product_code;
bool is_detection_hand;
uint32_t handId_;
};
} // namespace communication
} // namespace linkerhand
// 向后兼容:在全局命名空间中提供别名
namespace Communication = linkerhand::communication;
#endif // LINKERHAND_ETHERCAT_H
#endif
#ifndef LINKERHAND_HAND_FACTORY_H
#define LINKERHAND_HAND_FACTORY_H
#include <iostream>
#include <string>
#include <algorithm>
#include <cctype>
#include "IHand.h"
#include "LinkerHandL6.h"
#include "LinkerHandL7.h"
#include "LinkerHandL10.h"
#include "LinkerHandL20.h"
#include "LinkerHandL25.h"
#include "ModbusLinkerHandL10.h"
#include "Common.h"
namespace linkerhand {
namespace factory {
/**
* @brief 手型号工厂类
*
* 根据手型号、手类型和通信类型创建对应的手实例
*/
class HandFactory {
public:
/**
* @brief 创建手实例
* @param type 手型号
* @param handId 手ID(左手/右手)
* @param commType 通信类型
* @return 手实例的智能指针
* @throws std::invalid_argument 如果参数无效
*/
static std::unique_ptr<hand::IHand> createHand(LINKER_HAND type, uint32_t handId, COMM_TYPE commType) {
if (handId != static_cast<uint32_t>(LEFT) &&
handId != static_cast<uint32_t>(RIGHT))
{
throw std::invalid_argument("Unsupported hand type");
}
std::string canChannel;
int baudrate = 1000000;
switch (commType)
{
case COMM_CAN_0:
canChannel = "can0";
break;
case COMM_CAN_1:
canChannel = "can1";
break;
case COMM_MODBUS:
canChannel = "modbus";
break;
case COMM_ETHERCAT:
canChannel = "ethercat";
break;
default:
throw std::invalid_argument("Unknown comm type");
break;
}
if (canChannel == "can0" || canChannel == "can1" || canChannel == "ethercat") {
switch (type) {
case O6:
return std::unique_ptr<hand::IHand>(std::make_unique<hand::L6Hand>(handId, canChannel, baudrate));
break;
case L6:
return std::unique_ptr<hand::IHand>(std::make_unique<hand::L6Hand>(handId, canChannel, baudrate));
break;
case L7:
return std::unique_ptr<hand::IHand>(std::make_unique<hand::L7Hand>(handId, canChannel, baudrate));
break;
case L10:
return std::unique_ptr<hand::IHand>(std::make_unique<hand::L10Hand>(handId, canChannel, baudrate));
break;
case L20:
return std::unique_ptr<hand::IHand>(std::make_unique<hand::L20Hand>(handId, canChannel, baudrate));
break;
case L21:
return std::unique_ptr<hand::IHand>(std::make_unique<hand::L25Hand>(handId, canChannel, baudrate, 1));
break;
case L25:
return std::unique_ptr<hand::IHand>(std::make_unique<hand::L25Hand>(handId, canChannel, baudrate, 0));
break;
default:
throw std::invalid_argument("Unknown hand type");
}
} else if (canChannel == "modbus") {
#if USE_RMAN
switch (type) {
case L10:
return std::unique_ptr<hand::IHand>(std::make_unique<hand::ModbusL10Hand>(handId));
default:
throw std::invalid_argument("Unknown hand type");
break;
}
#else
throw std::runtime_error("ModBus support is disabled (USE_RMAN=0)");
#endif
}
return nullptr;
}
static std::unique_ptr<hand::IHand> createHand(LINKER_HAND type, uint32_t handId, const std::string canChannel, const int baudrate) {
if (handId != static_cast<uint32_t>(LEFT) &&
handId != static_cast<uint32_t>(RIGHT))
{
throw std::invalid_argument("Unsupported hand type");
}
if (containsIgnoreCase(canChannel, "can")) {
switch (type) {
case O6:
return std::unique_ptr<hand::IHand>(std::make_unique<hand::L6Hand>(handId, canChannel, baudrate));
break;
case L6:
return std::unique_ptr<hand::IHand>(std::make_unique<hand::L6Hand>(handId, canChannel, baudrate));
break;
case L7:
return std::unique_ptr<hand::IHand>(std::make_unique<hand::L7Hand>(handId, canChannel, baudrate));
break;
case L10:
return std::unique_ptr<hand::IHand>(std::make_unique<hand::L10Hand>(handId, canChannel, baudrate));
break;
case L20:
return std::unique_ptr<hand::IHand>(std::make_unique<hand::L20Hand>(handId, canChannel, baudrate));
break;
case L21:
return std::unique_ptr<hand::IHand>(std::make_unique<hand::L25Hand>(handId, canChannel, baudrate, 1));
break;
case L25:
return std::unique_ptr<hand::IHand>(std::make_unique<hand::L25Hand>(handId, canChannel, baudrate, 0));
break;
default:
throw std::invalid_argument("Unknown hand type");
}
} else if (canChannel == "modbus") {
#if USE_RMAN
switch (type) {
case L10:
return std::unique_ptr<hand::IHand>(std::make_unique<hand::ModbusL10Hand>(handId));
default:
throw std::invalid_argument("Unknown hand type");
break;
}
#else
throw std::runtime_error("ModBus support is disabled (USE_RMAN=0)");
#endif
}
return nullptr;
}
private:
static std::string toLower(const std::string& str) {
std::string result = str;
std::transform(result.begin(), result.end(), result.begin(), ::tolower);
return result;
}
static bool containsIgnoreCase(const std::string& str, const std::string& target) {
return toLower(str).find(toLower(target)) != std::string::npos;
}
};
} // namespace factory
} // namespace linkerhand
// 向后兼容:在全局命名空间中提供别名
using HandFactory = linkerhand::factory::HandFactory;
#endif // LINKERHAND_HAND_FACTORY_H
#ifndef LINKERHAND_ICANBUS_H
#define LINKERHAND_ICANBUS_H
#include <vector>
#include <cstdint>
#include <string>
#include "Common.h"
#include "CanFrame.h"
namespace linkerhand {
namespace communication {
/**
* @brief CAN 总线接口
*
* 定义 CAN 总线通信的标准接口
*/
class ICanBus {
public:
virtual ~ICanBus() = default;
/**
* @brief 发送 CAN 帧
* @param data 要发送的数据
* @param can_id CAN 帧 ID
* @param wait 是否等待发送完成(默认 false)
*/
virtual void send(const std::vector<uint8_t>& data, uint32_t can_id,
const bool wait = false) = 0;
/**
* @brief 接收 CAN 帧
* @param id CAN 帧 ID(输出参数)
* @return 接收到的 CAN 帧
*/
virtual CANFrame recv(uint32_t& id) = 0;
};
} // namespace communication
} // namespace linkerhand
// 向后兼容的别名
namespace Communication = linkerhand::communication;
#endif // LINKERHAND_ICANBUS_H
#ifndef LINKERHAND_ICOMMUNICATION_H
#define LINKERHAND_ICOMMUNICATION_H
#include <vector>
#include <cstdint>
namespace linkerhand {
namespace communication {
/**
* @brief 通信接口基类
*
* 定义通用的通信接口,支持 ModBus 等协议
*/
class ICommunication {
public:
virtual ~ICommunication() = default;
/**
* @brief 发送数据
* @param data 要发送的数据
* @param id 设备ID(输入输出参数)
* @param start_address 起始地址(默认0)
* @param num 数量(默认0)
*/
virtual void send(const std::vector<uint8_t>& data, uint32_t &id,
const int &start_address = 0, const int &num = 0) = 0;
/**
* @brief 接收数据
* @param id 设备ID(输入输出参数)
* @param start_address 起始地址(默认0)
* @param num 数量(默认0)
* @return 接收到的数据
*/
virtual std::vector<uint8_t> recv(uint32_t& id,
const int &start_address = 0,
const int &num = 0) = 0;
};
} // namespace communication
} // namespace linkerhand
#endif // LINKERHAND_ICOMMUNICATION_H
\ No newline at end of file
#ifndef LINKERHAND_IHAND_H
#define LINKERHAND_IHAND_H
#include <set>
#include <vector>
#include <string>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <chrono>
#include <stdexcept>
#include "RangeToArc.h"
#include "Common.h"
#include "ErrorCode.h"
namespace linkerhand {
namespace hand {
/**
* @brief 灵巧手接口基类
*
* 定义所有手型号必须实现的基础接口
* 注意:不支持的方法会抛出异常而不是返回空值
*/
class IHand
{
public:
virtual ~IHand() = default;
// 设置关节位置
virtual void setJointPositions(const std::vector<uint8_t> &jointAngles) = 0;
virtual void setJointPositionArc(const std::vector<double> &jointAngles)
{
(void) jointAngles;
throw UnsupportedFeatureException("setJointPositionArc");
}
// 获取速度数据
virtual std::vector<uint8_t> getSpeed()
{
throw UnsupportedFeatureException("getSpeed");
}
// 设置关节速度
virtual void setSpeed(const std::vector<uint8_t> &speed)
{
(void) speed;
throw UnsupportedFeatureException("setSpeed");
}
// 获取当前关节位置
virtual std::vector<uint8_t> getCurrentStatus()
{
throw UnsupportedFeatureException("getCurrentStatus");
}
virtual std::vector<double> getCurrentStatusArc()
{
throw UnsupportedFeatureException("getCurrentStatusArc");
}
// 获取电机故障码
virtual std::vector<uint8_t> getFaultCode()
{
throw UnsupportedFeatureException("getFaultCode");
}
// 获取电机电流
virtual std::vector<uint8_t> getCurrent()
{
throw UnsupportedFeatureException("getCurrent");
}
// ------------------------------------------------------
// 获取压感数据
virtual std::vector<std::vector<uint8_t>> getForce(const int type)
{
(void)type;
throw UnsupportedFeatureException("getForce");
}
virtual std::vector<std::vector<std::vector<uint8_t>>> getForce()
{
throw UnsupportedFeatureException("getForce");
}
// 获取大拇指压感数据
virtual std::vector<uint8_t> getThumbForce()
{
throw UnsupportedFeatureException("getThumbForce");
}
// 获取食指压感数据
virtual std::vector<uint8_t> getIndexForce()
{
throw UnsupportedFeatureException("getIndexForce");
}
// 获取中指压感数据
virtual std::vector<uint8_t> getMiddleForce()
{
throw UnsupportedFeatureException("getMiddleForce");
}
// 获取无名指压感数据
virtual std::vector<uint8_t> getRingForce()
{
throw UnsupportedFeatureException("getRingForce");
}
// 获取小拇指压感数据
virtual std::vector<uint8_t> getLittleForce()
{
throw UnsupportedFeatureException("getLittleForce");
}
// ------------------------------------------------------
// 获取五指法向力
virtual std::vector<uint8_t> getNormalForce()
{
throw UnsupportedFeatureException("getNormalForce");
}
// 获取五指切向力
virtual std::vector<uint8_t> getTangentialForce()
{
throw UnsupportedFeatureException("getTangentialForce");
}
// 获取五指法向力方向
virtual std::vector<uint8_t> getTangentialForceDir()
{
throw UnsupportedFeatureException("getTangentialForceDir");
}
// 获取五指接近感应
virtual std::vector<uint8_t> getApproachInc()
{
throw UnsupportedFeatureException("getApproachInc");
}
// ------------------------------------------------------
// 设置扭矩 L20暂不支持
virtual void setTorque(const std::vector<uint8_t> &torque)
{
(void)torque;
throw UnsupportedFeatureException("setTorque");
}
// 获取电机扭矩 L20暂不支持
virtual std::vector<uint8_t> getTorque()
{
throw UnsupportedFeatureException("getTorque");
}
// 获取电机温度 L20暂不支持
virtual std::vector<uint8_t> getTemperature()
{
throw UnsupportedFeatureException("getTemperature");
}
// 获取版本号 目前仅支持L10
virtual std::string getVersion()
{
throw UnsupportedFeatureException("getVersion");
}
// 获取设备ID L20协议
virtual std::vector<uint8_t> getUID()
{
throw UnsupportedFeatureException("getUID");
}
// 手指堵转或过流判断计数阀值 L20协议
virtual std::vector<uint8_t> getRotorLockCount()
{
throw UnsupportedFeatureException("getRotorLockCount");
}
// 清除电机故障码 目前仅支持L20
virtual void clearFaultCode(const std::vector<uint8_t> &code)
{
(void)code;
throw UnsupportedFeatureException("clearFaultCode");
}
// 设置电流 目前仅支持L20
virtual void setCurrent(const std::vector<uint8_t> &current)
{
(void)current;
throw UnsupportedFeatureException("setCurrent");
}
// 设置电机使能 目前仅支持L25
virtual void setMotorEnable(const std::vector<uint8_t> &enable)
{
(void)enable;
throw UnsupportedFeatureException("setMotorEnable");
}
// 设置电机使能 目前仅支持L25
virtual void setMotorDisable(const std::vector<uint8_t> &disable)
{
(void)disable;
throw UnsupportedFeatureException("setMotorDisable");
}
// ---------------------------------------------------------------------
virtual std::vector<uint8_t> getSubVector(const std::vector<uint8_t> &vec)
{
std::vector<uint8_t> result;
if (vec.size() > 1) result.insert(result.end(), vec.begin() + 1, vec.end());
return result;
}
virtual std::vector<uint8_t> getSubVector(const std::vector<uint8_t>& vec1, const std::vector<uint8_t>& vec2)
{
std::vector<uint8_t> result;
if (vec1.size() > 1) result.insert(result.end(), vec1.begin() + 1, vec1.end());
if (vec2.size() > 1) result.insert(result.end(), vec2.begin() + 1, vec2.end());
return result;
}
virtual std::string getCurrentTime()
{
// 获取当前时间点
auto now = std::chrono::high_resolution_clock::now();
// 使用std::put_time格式化时间点为字符串
std::time_t time = std::chrono::system_clock::to_time_t(now);
std::stringstream ss;
ss << std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S");
// 获取微秒部分并添加到字符串中
auto duration = now.time_since_epoch();
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(duration % std::chrono::seconds(1)).count();
// 只保留毫秒部分
int milliseconds = microseconds / 1000;
ss << "." << std::setfill('0') << std::setw(3) << milliseconds;
// 微妙
// ss << "." << std::setfill('0') << std::setw(6) << microseconds;
// 打印格式化的时间字符串
// std::cout << "send time: " << ss.str() << std::endl;
return ss.str();
}
};
} // namespace hand
} // namespace linkerhand
#endif // LINKERHAND_IHAND_H
#ifndef LINKERHAND_API_H
#define LINKERHAND_API_H
#include <iostream>
#include <vector>
#include <memory>
#include <string>
#include "HandFactory.h"
#include "Common.h"
#include "IHand.h"
/**
* @brief LinkerHand API 主类
*
* 提供统一的接口用于控制各种型号的灵巧手设备
*
* 注意:为了与预编译库兼容,此类保持在全局命名空间
*/
class LinkerHandApi
{
public:
/**
* @brief 构造函数
* @param handModel 手型号(LINKER_HAND 枚举)
* @param handType 手类型(左手/右手)
* @param commType 通信类型(默认 CAN_0)
*/
LinkerHandApi(const LINKER_HAND &handModel, const HAND_TYPE &handType,
const COMM_TYPE commType = COMM_CAN_0);
LinkerHandApi(const LINKER_HAND &handModel, const HAND_TYPE &handType,
const std::string canChannel, const int baudrate);
~LinkerHandApi();
// 设置关节位置
void fingerMove(const std::vector<uint8_t> &pose);
void fingerMoveArc(const std::vector<double> &pose);
// 设置速度
void setSpeed(const std::vector<uint8_t> &speed);
// 设置扭矩
void setTorque(const std::vector<uint8_t> &torque);
// 获取压感
std::vector<std::vector<std::vector<uint8_t>>> getForce();
// 获取速度
std::vector<uint8_t> getSpeed();
// 获取当前关节状态
std::vector<uint8_t> getState();
std::vector<double> getStateArc();
// 获取版本号
std::string getVersion();
// ----------------------------------------------------------
// 获取电机温度
std::vector<uint8_t> getTemperature();
// 获取电机故障码
std::vector<uint8_t> getFaultCode();
// 获取当前电流
std::vector<uint8_t> getCurrent();
// 获取当前最大扭矩
std::vector<uint8_t> getTorque();
// ------------------------- 待开发 --------------------------
// 设置电流 目前仅支持L20
void setCurrent(const std::vector<uint8_t> &current);
// 清除电机故障码 目前仅支持L20
void clearFaultCode(const std::vector<uint8_t> &torque = std::vector<uint8_t>(5, 1));
// 设置电机使能 目前仅支持L25
void setEnable(const std::vector<uint8_t> &enable = std::vector<uint8_t>(5, 0));
// 设置电机使能 目前仅支持L25
void setDisable(const std::vector<uint8_t> &disable = std::vector<uint8_t>(5, 1));
private:
// 获取法向压力
void getNormalForce();
// 获取切向压力
void getTangentialForce();
// 获取切向压力方向
void getTangentialForceDir();
// 获取接近感应
void getApproachInc();
private:
std::unique_ptr<linkerhand::hand::IHand> hand;
uint32_t handId;
public:
LINKER_HAND handModel_; // 手型号(L6, L7, L10, L20, L21, L25 等)
HAND_TYPE handType_;
};
// 在新命名空间中提供类型别名(用于未来版本)
namespace linkerhand {
namespace api {
using LinkerHandApi = ::LinkerHandApi;
} // namespace api
} // namespace linkerhand
#endif // LINKERHAND_API_H
#ifndef LINKERHAND_L10_H
#define LINKERHAND_L10_H
#include <thread>
#include <mutex>
#include <queue>
#include <iostream>
#include <sstream>
#include <chrono>
#include <condition_variable>
#include "IHand.h"
#include "CanBusFactory.h"
namespace linkerhand {
namespace hand {
// L10 型号的帧属性枚举
enum class L10FrameProperty
{
// INVALID_FRAME_PROPERTY = 0x00, // 无效的can帧属性
JOINT_POSITION_RCO = 0x01, // 关节1-6的关节位置
TORQUE_LIMIT = 0x02, // 五根手指的转矩限制
TORQUE_LIMIT_2 = 0x03, // 五根手指的转矩限制2
JOINT_POSITION2_RCO = 0x04, // 关节7-10的关节位置
JOINT_SPEED = 0x05, // 五根手指的速度
JOINT_SPEED_2 = 0x06, // 五根手指的速度2
REQUEST_DATA_RETURN = 0x09, // 获取所有关节位置和压力
// JOINT_POSITION_N = 0x11,
// MAX_PRESS_N = 0x12,
HAND_NORMAL_FORCE = 0X20, // 五个手指的法向压力
HAND_TANGENTIAL_FORCE = 0X21, // 五个手指的切向压力
HAND_TANGENTIAL_FORCE_DIR = 0X22, // 五个手指的切向方向
HAND_APPROACH_INC = 0X23, // 五个手指指接近感应
THUMB_ALL_DATA = 0x28, // 大拇指所有数据 | 返回 1法向压力 2切向压力 3切向方向 4接近感应
INDEX_FINGER_ALL_DATA = 0x29, // 食指所有数据 | 返回 1法向压力 2切向压力 3切向方向 4接近感应
MIDDLE_FINGER_ALL_DATA = 0x30, // 中指所有数据 | 返回 1法向压力 2切向压力 3切向方向 4接近感应
RING_FINGER_ALL_DATA = 0x31, // 无名指所有数据 | 返回 1法向压力 2切向压力 3切向方向 4接近感应
LITTLE_FINGER_ALL_DATA = 0x32, // 小拇指所有数据 | 返回 1法向压力 2切向压力 3切向方向 4接近感应
MOTOR_TEMPERATURE_1 = 0x33, // 电机温度1
MOTOR_TEMPERATURE_2 = 0x34, // 电机温度2
MOTOR_FAULT_CODE_1 = 0x35, // 电机故障码1
MOTOR_FAULT_CODE_2 = 0x36, // 电机故障码2
LINKER_HAND_VERSION = 0X64, // 版本号
// 新压感
TOUCH_SENSOR_TYPE = 0xB0, // 触觉传感器类型
THUMB_TOUCH = 0xB1, // 大拇指触觉传感
INDEX_TOUCH = 0xB2, // 食指触觉传感
MIDDLE_TOUCH = 0xB3, // 中指触觉传感
RING_TOUCH = 0xB4, // 无名指触觉传感
LITTLE_TOUCH = 0xB5, // 小拇指触觉传感
PALM_TOUCH = 0xB6, // 手掌指触觉传感
};
/**
* @brief L10 型号灵巧手实现类
*
* 提供 L10 型号的所有功能实现
*/
class L10Hand : public IHand
{
public:
L10Hand(uint32_t handId, const std::string &canChannel, int baudrate);
~L10Hand();
// 设置关节位置
void setJointPositions(const std::vector<uint8_t> &jointAngles) override;
void setJointPositionArc(const std::vector<double> &jointAngles) override;
// 设置最大扭矩
void setTorque(const std::vector<uint8_t> &torque) override;
// 设置关节速度
void setSpeed(const std::vector<uint8_t> &speed) override;
// 获取当前速度
std::vector<uint8_t> getSpeed() override;
// 获取当前关节状态
std::vector<uint8_t> getCurrentStatus() override;
std::vector<double> getCurrentStatusArc() override;
//--------------------------------------------------------------------
// 获取所有压感数据
std::vector<std::vector<std::vector<uint8_t>>> getForce() override;
// 获取五个手指的法向压力
std::vector<uint8_t> getNormalForce() override;
// 获取五个手指的切向压力
std::vector<uint8_t> getTangentialForce() override;
// 获取五个手指的切向方向
std::vector<uint8_t> getTangentialForceDir() override;
// 获取五个手指指接近感应
std::vector<uint8_t> getApproachInc() override;
//--------------------------------------------------------------------
// 获取版本信息
std::string getVersion() override;
// 获取电机温度
std::vector<uint8_t> getTemperature() override;
// 获取电机故障码
std::vector<uint8_t> getFaultCode() override;
// 获取电机电流
std::vector<uint8_t> getCurrent() override; // 暂时无用
// 获取所有关节位置和压力
std::vector<uint8_t> requestAllStatus(); // 暂时无用
// 获取当前扭矩
std::vector<uint8_t> getTorque() override;
private:
uint32_t handId;
std::unique_ptr<Communication::ICanBus> bus;
std::thread receiveThread;
bool running;
std::mutex responseMutex;
void receiveResponse();
std::queue<std::vector<uint8_t>> responseQueue; // 通用响应队列
std::condition_variable queueCond; // 通用队列条件变量
std::mutex queueMutex; // 通用队列互斥锁
// 队列和条件变量
std::vector<uint8_t> joint_position;
std::vector<uint8_t> joint_position2;
std::vector<uint8_t> joint_speed;
std::vector<uint8_t> joint_speed_2;
std::vector<uint8_t> normal_force;
std::vector<uint8_t> tangential_force;
std::vector<uint8_t> tangential_force_dir;
std::vector<uint8_t> approach_inc;
std::vector<uint8_t> thumb_pressure;
std::vector<uint8_t> index_finger_pressure;
std::vector<uint8_t> middle_finger_pressure;
std::vector<uint8_t> ring_finger_pressure;
std::vector<uint8_t> little_finger_pressure;
std::vector<std::vector<std::vector<uint8_t>>> touch_mats;
// 最大扭矩
std::vector<uint8_t> max_torque;
std::vector<uint8_t> max_torque_2;
// 电机温度
std::vector<uint8_t> motorTemperature_1;
std::vector<uint8_t> motorTemperature_2;
// 电机故障码
std::vector<uint8_t> motorFaultCode_1;
std::vector<uint8_t> motorFaultCode_2;
// 版本信息
std::vector<uint8_t> version;
uint8_t sensor_type = 0;
};
} // namespace hand
} // namespace linkerhand
// 向后兼容:在旧命名空间中提供别名
namespace LinkerHandL10 {
using FRAME_PROPERTY = linkerhand::hand::L10FrameProperty;
using LinkerHand = linkerhand::hand::L10Hand;
} // namespace LinkerHandL10
#endif // LINKERHAND_L10_H
\ No newline at end of file
#ifndef LINKERHAND_L20_H
#define LINKERHAND_L20_H
#include <thread>
#include <mutex>
#include <queue>
#include <iostream>
#include <sstream>
#include <condition_variable>
#include "IHand.h"
#include "CanBusFactory.h"
namespace linkerhand {
namespace hand {
// L20 型号的帧属性枚举
enum class L20FrameProperty
{
INVALID_FRAME_PROPERTY = 0x00, // 无效的can帧属性 | 无返回
JOINT_PITCH_R = 0x01, // 短帧俯仰角-手指根部弯曲程度 | 返回本类型数据
JOINT_YAW_R = 0x02, // 短帧航向角-手指横摆,控制间隙 | 返回本类型数据
JOINT_ROLL_R = 0x03, // 短帧横滚角-只有大拇指副用到了 | 返回本类型数据
JOINT_TIP_R = 0x04, // 短帧指尖角度弯曲程度 | 返回本类型数据
JOINT_SPEED_R = 0x05, // 短帧速度 电机运行速度控制 | 返回本类型数据
JOINT_CURRENT_R = 0x06, // 短帧电流 电机运行电流 | 返回本类型数据
JOINT_FAULT_R = 0x07, // 短帧故障 电机运行故障 1清除 0查询 | 返回本类型数据
ROTOR_LOCK_COUNT = 0x08, // 手指堵转或过流判断计数阀值 | 返回本类型数据
REQUEST_DATA_RETURN = 0x09, // 请求数据返回 | 返回所有手指控制数据,返回数据会分若干帧发出。
JOINT_PITCH_NR = 0x11, // 俯仰角-手指根部弯曲 | 不返回本类型数据
JOINT_YAW_NR = 0x12, // 航向角-手指横摆,控制间隙 | 不返回本类型数据
JOINT_ROLL_NR = 0x13, // 横滚角-只有大拇指副用到了 | 不返回本类型数据
JOINT_TIP_NR = 0x14, // 指尖角度控制 | 不返回本类型数据
JOINT_SPEED_NR = 0x15, // 速度 电机运行速度控制 | 不返回本类型数据
JOINT_CURRENT_NR = 0x16, // 电流 电机运行电流反馈 | 不返回本类型数据
JOINT_FAULT_NR = 0x17, // 故障 电机运行故障反馈 | 不返回本类型数据
HAND_NORMAL_FORCE = 0X20, // 返回五个手指的法向压力
HAND_TANGENTIAL_FORCE = 0X21, // 返回五个手指的切向压力
HAND_TANGENTIAL_FORCE_DIR = 0X22, // 返回五个手指的切向方向 数值0-127对应实际切向力角度0-359,无切向力保持255
HAND_APPROACH_INC = 0X23, // 返回五个手指指接近感应
THUMB_ALL_DATA = 0x30, // 大拇指所有数据 | 返回 1法向压力 2切向压力 3切向方向 4接近感应
INDEX_FINGER_ALL_DATA = 0x31, // 食指所有数据 | 返回 1法向压力 2切向压力 3切向方向 4接近感应
MIDDLE_FINGER_ALL_DATA = 0x32, // 中指所有数据 | 返回 1法向压力 2切向压力 3切向方向 4接近感应
RING_FINGER_ALL_DATA = 0x33, // 无名指所有数据 | 返回 1法向压力 2切向压力 3切向方向 4接近感应
LITTLE_FINGER_ALL_DATA = 0x34, // 小拇指所有数据 | 返回 1法向压力 2切向压力 3切向方向 4接近感应
HAND_UID = 0xC0, // 设备唯一标识码 只读 --------
HAND_HARDWARE_VERSION = 0xC1, // 硬件版本 只读 --------
HAND_SOFTWARE_VERSION = 0xC2, // 软件版本 只读 --------
HAND_COMM_ID = 0xC3, // 设备ID 可读写 1字节
HAND_SAVE_PARAMETER = 0xCF, // 保存参数 只写 --------
// 新压感
TOUCH_SENSOR_TYPE = 0xB0, // 触觉传感器类型
THUMB_TOUCH = 0xB1, // 大拇指触觉传感
INDEX_TOUCH = 0xB2, // 食指触觉传感
MIDDLE_TOUCH = 0xB3, // 中指触觉传感
RING_TOUCH = 0xB4, // 无名指触觉传感
LITTLE_TOUCH = 0xB5, // 小拇指触觉传感
PALM_TOUCH = 0xB6 // 手掌指触觉传感
};
/**
* @brief L20 型号灵巧手实现类
*
* 提供 L20 型号的所有功能实现
*/
class L20Hand : public IHand {
public:
L20Hand(uint32_t handId, const std::string& canChannel, int baudrate);
~L20Hand();
// 设置关节位置
void setJointPositions(const std::vector<uint8_t> &jointAngles) override;
void setJointPositionArc(const std::vector<double> &jointAngles) override;
// 设置关节速度
void setSpeed(const std::vector<uint8_t> &speed) override;
// 获取当前速度
std::vector<uint8_t> getSpeed() override;
// 获取当前关节状态
std::vector<uint8_t> getCurrentStatus() override;
std::vector<double> getCurrentStatusArc() override;
//--------------------------------------------------------------------
// 获取所有压感数据
std::vector<std::vector<std::vector<uint8_t>>> getForce() override;
// 获取五个手指的法向压力
std::vector<uint8_t> getNormalForce() override;
// 获取五个手指的切向压力
std::vector<uint8_t> getTangentialForce() override;
// 获取五个手指的切向方向
std::vector<uint8_t> getTangentialForceDir() override;
// 获取五个手指指接近感应
std::vector<uint8_t> getApproachInc() override;
#if 0
// 获取大拇指压感数据
std::vector<uint8_t> getThumbForce() override;
// 获取食指压感数据
std::vector<uint8_t> getIndexForce() override;
// 获取中指压感数据
std::vector<uint8_t> getMiddleForce() override;
// 获取无名指压感数据
std::vector<uint8_t> getRingForce() override;
// 获取小拇指压感数据
std::vector<uint8_t> getLittleForce() override;
#endif
//--------------------------------------------------------------------
// 获取电机故障码
std::vector<uint8_t> getFaultCode() override;
// 获取电机电流
std::vector<uint8_t> getCurrent() override;
// 清除电机故障码
void clearFaultCode(const std::vector<uint8_t> &code = std::vector<uint8_t>(5, 1)) override;
// 设置电流
void setCurrent(const std::vector<uint8_t> &current) override;
// 获取版本号
std::string getVersion() override;
// 获取设备唯一标志
std::vector<uint8_t> getUID() override;
// 获取堵转计数
std::vector<uint8_t> getRotorLockCount() override;
private:
uint32_t handId;
std::unique_ptr<Communication::ICanBus> bus;
std::thread receiveThread;
bool running;
std::mutex responseMutex;
void receiveResponse();
// 故障码
std::vector<uint8_t> motor_fault_code;
// 电流
std::vector<uint8_t> motor_current;
// 速度
std::vector<uint8_t> motor_speed;
// 压感数据
std::vector<std::vector<uint8_t>> force_data;
std::vector<uint8_t> normal_force;
std::vector<uint8_t> tangential_force;
std::vector<uint8_t> tangential_force_dir;
std::vector<uint8_t> approach_inc;
std::vector<uint8_t> thumb_pressure;
std::vector<uint8_t> index_finger_pressure;
std::vector<uint8_t> middle_finger_pressure;
std::vector<uint8_t> ring_finger_pressure;
std::vector<uint8_t> little_finger_pressure;
std::vector<std::vector<std::vector<uint8_t>>> touch_mats;
// 关节位置
std::vector<uint8_t> joint_position1;
std::vector<uint8_t> joint_position2;
std::vector<uint8_t> joint_position3;
std::vector<uint8_t> joint_position4;
// 版本号
std::vector<uint8_t> hand_hardware_version;
std::vector<uint8_t> hand_software_version;
// 设备ID
std::vector<uint8_t> hand_comm_id;
// 设备唯一标志
std::vector<uint8_t> hand_uid;
// 堵转计数
std::vector<uint8_t> rotor_lock_count;
uint8_t sensor_type = 0;
};
} // namespace hand
} // namespace linkerhand
// 向后兼容:在旧命名空间中提供别名
namespace LinkerHandL20 {
using FRAME_PROPERTY = linkerhand::hand::L20FrameProperty;
using LinkerHand = linkerhand::hand::L20Hand;
} // namespace LinkerHandL20
#endif // LINKERHAND_L20_H
\ No newline at end of file
This diff is collapsed.
#ifndef LINKERHAND_L6_H
#define LINKERHAND_L6_H
#include <thread>
#include <mutex>
#include <queue>
#include <chrono>
#include <iostream>
#include <sstream>
#include <condition_variable>
#include "IHand.h"
#include "CanBusFactory.h"
namespace linkerhand {
namespace hand {
// L6/O6 型号的帧属性枚举
enum class L6FrameProperty
{
// 指令码 指令功能 数据长度 CAN发送DLC CAN接收DLC 数据范围
JOINT_POSITION = 0x01, // 关节1-6的关节位置 6 7 7 0-0xFF
TORQUE_LIMIT = 0x02, // 关节1-6的转矩限制 6 7 7 0-0xFF
JOINT_SPEED = 0x05, // 关节1-6的速度 6 7 7 0-0xFF
JOINT_ACCELERATION = 0x07, // 关节1-6的加速度 6 7 7 0-0xFE
MOTOR_TEMPERATURE = 0x33, // 关节1-6的温度信息 7 1 8 0-0xFF
MOTOR_ERROR_CODE = 0x35, // 关节1-6的错误码 7 1 8 0-0xFF
CLEAR_FAULT_CODE = 0x83, // 清除关节1-6的错误码 6 7 7 0-0xFF
RE_HOME_COMMAND = 0x38, // 重新调零点命令 8 8 2
// 设备信息指令
DEVICE_SERIAL_NUMBER = 0xC0, // 设备出厂编码 24 1 3帧8字节
DEVICE_HARDWARE_VERSION = 0xC1, // 设备硬件PCB版本号 3 1 4
DEVICE_SOFTWARE_VERSION = 0xC2, // 设备软件版本号 3 1 4
DEVICE_MECHANICAL_VERSION = 0xC4, // 设备机械结构版本号 3 1 4
MODIFY_CAN_ID = 0xC3, // 修改CANID指令 3
STALL_THRESHOLD = 0xC5, // 堵转阈值
STALL_TIME = 0xC6, // 堵转时间
STALL_TORQUE = 0xC7, // 堵转扭矩
FACTORY_RESET = 0xCE, // 恢复出厂设置
SAVE_PARAMETERS = 0xCF, // 保存参数
// 触觉传感器指令
TOUCH_SENSOR_TYPE = 0xB0, // 触觉传感器类型
THUMB_TOUCH = 0xB1, // 大拇指触觉传感
INDEX_TOUCH = 0xB2, // 食指触觉传感
MIDDLE_TOUCH = 0xB3, // 中指触觉传感
RING_TOUCH = 0xB4, // 无名指触觉传感
LITTLE_TOUCH = 0xB5, // 小拇指触觉传感
PALM_TOUCH = 0xB6 // 手掌触觉传感
};
// 协议辅助常量
static constexpr uint8_t TOUCH_TYPE_MATRIX = 0x02; // 矩阵型触觉类型值
static constexpr uint8_t TOUCH_PAGE_REQ = 0xC6; // 触觉分页读取的请求子命令
/**
* @brief L6/O6 型号灵巧手实现类
*
* 提供 L6 和 O6 型号的所有功能实现
*/
class L6Hand : public IHand
{
public:
L6Hand(uint32_t handId, const std::string &canChannel, int baudrate);
~L6Hand();
// 设置关节位置
void setJointPositions(const std::vector<uint8_t> &jointAngles) override;
void setJointPositionArc(const std::vector<double> &jointAngles) override;
void setSpeed(const std::vector<uint8_t> &speed) override;
void setTorque(const std::vector<uint8_t> &torque);
void setAcceleration(const std::vector<uint8_t> &acceleration);
// 重新调零
void reHome();
// 获取当前关节状态
std::vector<uint8_t> getCurrentStatus() override;
std::vector<double> getCurrentStatusArc() override;
// 获取当前速度
std::vector<uint8_t> getSpeed() override;
// 获取当前扭矩
std::vector<uint8_t> getTorque() override;
// 获取当前加速度
std::vector<uint8_t> getAcceleration();
// 获取电机错误码
std::vector<uint8_t> getFaultCode() override;
void clearFaultCode(const std::vector<uint8_t> &code);
// 获取压感数据
std::vector<std::vector<std::vector<uint8_t>>> getForce() override;
// 获取电机温度
std::vector<uint8_t> getTemperature() override;
// 获取版本信息
std::string getVersion() override;
// 获取设备序列号
std::string getSerialNumber();
// 获取硬件版本
std::string getHardwareVersion();
// 获取机械版本
std::string getMechanicalVersion();
// 系统功能
void factoryReset();
void saveParameters();
private:
uint32_t handId;
std::unique_ptr<Communication::ICanBus> bus;
std::thread receiveThread;
bool running;
std::mutex data_mutex_;
void receiveResponse();
// 数据存储
std::vector<uint8_t> joint_position;
std::vector<uint8_t> joint_speeds;
std::vector<uint8_t> joint_torques;
std::vector<uint8_t> joint_accelerations;
std::vector<uint8_t> motor_temperature;
std::vector<uint8_t> error_codes;
std::vector<uint8_t> device_serial;
std::vector<uint8_t> hardware_version;
std::vector<uint8_t> software_version;
std::vector<uint8_t> mechanical_version;
std::vector<std::vector<std::vector<uint8_t>>> touch_mats;
uint8_t sensor_type = 0;
// 辅助方法
void requestDeviceInfo();
void parseSerialNumber(const std::vector<uint8_t>& data);
std::string getErrorDescription(uint8_t error_code);
};
} // namespace hand
} // namespace linkerhand
// 向后兼容:在旧命名空间中提供别名
namespace LinkerHandL6 {
using FRAME_PROPERTY = linkerhand::hand::L6FrameProperty;
using LinkerHand = linkerhand::hand::L6Hand;
} // namespace LinkerHandL6
#endif // LINKERHAND_L6_H
#ifndef LINKERHAND_L7_H
#define LINKERHAND_L7_H
#include <thread>
#include <mutex>
#include <queue>
#include <chrono>
#include <iostream>
#include <sstream>
#include <condition_variable>
#include "IHand.h"
#include "CanBusFactory.h"
namespace linkerhand {
namespace hand {
// L7 型号的帧属性枚举
enum class L7FrameProperty
{
// 指令码 指令功能 数据长度 CAN发送DLC CAN接收DLC 数据范围
JOINT_POSITION = 0x01, // 关节1-7的关节位置 7 8 8 0-0xFF
TORQUE_LIMIT = 0x02, // 关节1-7的转矩限制 7 8 8 0-0xFF
JOINT_SPEED = 0x05, // 关节1-7的速度 7 8 8 0-0xFF
HAND_NORMAL_FORCE = 0x20, // 五个手指的法向压力 5 1 6 0-0xFF
HAND_TANGENTIAL_FORCE = 0x21, // 五个手指的切向压力 5 1 6 0-0xFF
HAND_TANGENTIAL_FORCE_DIR = 0x22, // 五个手指的切向方向 5 1 6 0-0x7F 0xFF
HAND_APPROACH_INC = 0x23, // 五个手指指接近感应 5 1 6 0-0xFF
THUMB_ALL_DATA = 0x28, // 大拇指所有压力数据 4 1 5 0-0xFF
INDEX_FINGER_ALL_DATA = 0x29, // 食指所有压力数据 4 1 5 0-0xFF
MIDDLE_FINGER_ALL_DATA = 0x30, // 中指所有压力数据 4 1 5 0-0xFF
RING_FINGER_ALL_DATA = 0x31, // 无名指所有压力数据 4 1 5 0-0xFF
LITTLE_FINGER_ALL_DATA = 0x32, // 小拇指所有压力数据 4 1 5 0-0xFF
MOTOR_TEMPERATURE = 0x33, // 关节1-7的温度信息 7 1 8 0-0xFF
MOTOR_FAULT_CODE = 0x35, // 关节1-7的错误码 7 1 8 0-0xFF
RESET_ZERO_COMMAND = 0x38, // 重新调零点命令 1 1 2
LINKER_HAND_VERSION = 0x64, // 版本号 8 1 8 0-0xFF
// 新压感
TOUCH_SENSOR_TYPE = 0xB0, // 触觉传感器类型
THUMB_TOUCH = 0xB1, // 大拇指触觉传感
INDEX_TOUCH = 0xB2, // 食指触觉传感
MIDDLE_TOUCH = 0xB3, // 中指触觉传感
RING_TOUCH = 0xB4, // 无名指触觉传感
LITTLE_TOUCH = 0xB5, // 小拇指触觉传感
PALM_TOUCH = 0xB6 // 手掌指触觉传感
};
/**
* @brief L7 型号灵巧手实现类
*
* 提供 L7 型号的所有功能实现
*/
class L7Hand : public IHand
{
public:
L7Hand(uint32_t handId, const std::string &canChannel, int baudrate);
~L7Hand();
// 设置关节位置
void setJointPositions(const std::vector<uint8_t> &jointAngles) override;
void setJointPositionArc(const std::vector<double> &jointAngles) override;
// 设置最大扭矩
void setTorque(const std::vector<uint8_t> &torque) override;
// 设置关节速度
void setSpeed(const std::vector<uint8_t> &speed) override;
// 获取当前速度
std::vector<uint8_t> getSpeed() override;
// 获取当前扭矩
std::vector<uint8_t> getTorque() override;
// 获取当前关节状态
std::vector<uint8_t> getCurrentStatus() override;
std::vector<double> getCurrentStatusArc() override;
//--------------------------------------------------------------------
// 获取所有压感数据
std::vector<std::vector<std::vector<uint8_t>>> getForce() override;
#if 1
// 获取五个手指的法向压力
std::vector<uint8_t> getNormalForce() override;
// 获取五个手指的切向压力
std::vector<uint8_t> getTangentialForce() override;
// 获取五个手指的切向方向
std::vector<uint8_t> getTangentialForceDir() override;
// 获取五个手指指接近感应
std::vector<uint8_t> getApproachInc() override;
#endif
//--------------------------------------------------------------------
// 获取电机温度
std::vector<uint8_t> getTemperature() override;
// 获取电机故障码
std::vector<uint8_t> getFaultCode() override;
// 获取版本信息
std::string getVersion() override;
private:
uint32_t handId;
std::unique_ptr<Communication::ICanBus> bus;
std::thread receiveThread;
bool running;
void receiveResponse();
// 队列和条件变量
std::vector<uint8_t> joint_position;
std::vector<uint8_t> joint_position2;
std::vector<uint8_t> joint_speed;
std::vector<uint8_t> normal_force;
std::vector<uint8_t> tangential_force;
std::vector<uint8_t> tangential_force_dir;
std::vector<uint8_t> approach_inc;
std::vector<uint8_t> thumb_pressure;
std::vector<uint8_t> index_finger_pressure;
std::vector<uint8_t> middle_finger_pressure;
std::vector<uint8_t> ring_finger_pressure;
std::vector<uint8_t> little_finger_pressure;
std::vector<std::vector<std::vector<uint8_t>>> touch_mats;
// 最大扭矩
std::vector<uint8_t> max_torque;
// 电机温度
std::vector<uint8_t> motorTemperature_1;
std::vector<uint8_t> motorTemperature_2;
// 电机故障码
std::vector<uint8_t> motorFaultCode_1;
std::vector<uint8_t> motorFaultCode_2;
// 版本信息
std::vector<uint8_t> version;
uint8_t sensor_type = 0;
};
} // namespace hand
} // namespace linkerhand
// 向后兼容:在旧命名空间中提供别名
namespace LinkerHandL7 {
using FRAME_PROPERTY = linkerhand::hand::L7FrameProperty;
using LinkerHand = linkerhand::hand::L7Hand;
} // namespace LinkerHandL7
#endif // LINKERHAND_L7_H
\ No newline at end of file
#ifndef LINKERHAND_LOGGER_H
#define LINKERHAND_LOGGER_H
#include <iostream>
#include <string>
#include <sstream>
#include <mutex>
#include <memory>
namespace linkerhand {
namespace logging {
/**
* @brief 日志级别枚举
*/
enum class LogLevel {
TRACE = 0,
DEBUG,
INFO,
WARN,
ERROR,
CRITICAL,
OFF
};
/**
* @brief 日志接口
*
* 定义日志系统的接口,可以替换为 spdlog 或其他日志库
*/
class ILogger {
public:
virtual ~ILogger() = default;
virtual void log(LogLevel level, const std::string& message) = 0;
virtual void setLevel(LogLevel level) = 0;
virtual LogLevel getLevel() const = 0;
void trace(const std::string& message) { log(LogLevel::TRACE, message); }
void debug(const std::string& message) { log(LogLevel::DEBUG, message); }
void info(const std::string& message) { log(LogLevel::INFO, message); }
void warn(const std::string& message) { log(LogLevel::WARN, message); }
void error(const std::string& message) { log(LogLevel::ERROR, message); }
void critical(const std::string& message) { log(LogLevel::CRITICAL, message); }
};
/**
* @brief 简单控制台日志实现
*
* 使用 std::cout 和 std::cerr 输出日志
* 可以后续替换为 spdlog
*/
class ConsoleLogger : public ILogger {
public:
explicit ConsoleLogger(LogLevel level = LogLevel::INFO)
: currentLevel_(level) {}
void log(LogLevel level, const std::string& message) override {
if (level < currentLevel_ || level == LogLevel::OFF) {
return;
}
std::lock_guard<std::mutex> lock(mutex_);
const char* levelStr = levelToString(level);
std::ostream& stream = (level >= LogLevel::ERROR) ? std::cerr : std::cout;
stream << "[" << levelStr << "] " << message << std::endl;
}
void setLevel(LogLevel level) override {
std::lock_guard<std::mutex> lock(mutex_);
currentLevel_ = level;
}
LogLevel getLevel() const override {
return currentLevel_;
}
private:
static const char* levelToString(LogLevel level) {
switch (level) {
case LogLevel::TRACE: return "TRACE";
case LogLevel::DEBUG: return "DEBUG";
case LogLevel::INFO: return "INFO";
case LogLevel::WARN: return "WARN";
case LogLevel::ERROR: return "ERROR";
case LogLevel::CRITICAL: return "CRITICAL";
default: return "UNKNOWN";
}
}
LogLevel currentLevel_;
mutable std::mutex mutex_;
};
/**
* @brief 空日志实现(禁用日志)
*/
class NullLogger : public ILogger {
public:
void log(LogLevel, const std::string&) override {}
void setLevel(LogLevel) override {}
LogLevel getLevel() const override { return LogLevel::OFF; }
};
/**
* @brief 全局日志管理器
*/
class Logger {
public:
/**
* @brief 获取默认日志实例
*/
static ILogger& get() {
static std::unique_ptr<ILogger> instance =
std::make_unique<ConsoleLogger>(LogLevel::INFO);
return *instance;
}
/**
* @brief 设置日志实例
*/
static void setLogger(std::unique_ptr<ILogger> logger) {
static std::unique_ptr<ILogger> instance = std::move(logger);
get() = *instance;
}
/**
* @brief 便捷方法:记录 TRACE 日志
*/
static void trace(const std::string& message) {
get().trace(message);
}
/**
* @brief 便捷方法:记录 DEBUG 日志
*/
static void debug(const std::string& message) {
get().debug(message);
}
/**
* @brief 便捷方法:记录 INFO 日志
*/
static void info(const std::string& message) {
get().info(message);
}
/**
* @brief 便捷方法:记录 WARN 日志
*/
static void warn(const std::string& message) {
get().warn(message);
}
/**
* @brief 便捷方法:记录 ERROR 日志
*/
static void error(const std::string& message) {
get().error(message);
}
/**
* @brief 便捷方法:记录 CRITICAL 日志
*/
static void critical(const std::string& message) {
get().critical(message);
}
/**
* @brief 设置日志级别
*/
static void setLevel(LogLevel level) {
get().setLevel(level);
}
/**
* @brief 获取日志级别
*/
static LogLevel getLevel() {
return get().getLevel();
}
};
} // namespace logging
} // namespace linkerhand
// 便捷宏定义
#define LINKERHAND_LOG_TRACE(msg) linkerhand::logging::Logger::trace(msg)
#define LINKERHAND_LOG_DEBUG(msg) linkerhand::logging::Logger::debug(msg)
#define LINKERHAND_LOG_INFO(msg) linkerhand::logging::Logger::info(msg)
#define LINKERHAND_LOG_WARN(msg) linkerhand::logging::Logger::warn(msg)
#define LINKERHAND_LOG_ERROR(msg) linkerhand::logging::Logger::error(msg)
#define LINKERHAND_LOG_CRITICAL(msg) linkerhand::logging::Logger::critical(msg)
#endif // LINKERHAND_LOGGER_H
#ifndef LINKERHAND_MOD_BUS_H
#define LINKERHAND_MOD_BUS_H
#if USE_RMAN
#include <cstdint>
#include <vector>
#include <string>
#include <iostream>
#include <mutex>
#include <atomic>
#include <unistd.h>
#include <chrono>
#include <thread>
#include <queue>
#include <unistd.h>
#include <fcntl.h>
#include <iomanip>
#include <sstream>
#include "rm_service.h"
#include "Common.h"
#include "ICommunication.h"
namespace linkerhand {
namespace communication {
/**
* @brief ModBus 通信实现
*/
class ModBus : public ICommunication
{
public:
ModBus(uint32_t handId);
~ModBus();
void send(const std::vector<uint8_t>& data, uint32_t &id, const int &start_address = 0, const int &num = 0) override;
std::vector<uint8_t> recv(uint32_t& id, const int &start_address = 0, const int &num = 0) override;
bool writeHoldingRegister(const int &id, const int &start_address, const int &num, const std::vector<uint8_t> &data);
std::vector<uint8_t> readHoldingRegister(const int &id, const int &start_address, const int &num);
private:
uint32_t handId;
int result = -1;
rm_robot_handle *robot_handle;
RM_Service robotic_arm;
std::mutex send_mutex;
};
} // namespace communication
} // namespace linkerhand
// 向后兼容:在全局命名空间中提供别名
namespace Communication = linkerhand::communication;
#endif
#endif // LINKERHAND_MOD_BUS_H
#ifndef LINKERHAND_MODBUS_L10_H
#define LINKERHAND_MODBUS_L10_H
#if USE_RMAN
#include <thread>
#include <mutex>
#include <queue>
#include <iostream>
#include <sstream>
#include <vector>
#include <condition_variable>
#include "ModBus.h"
#include "IHand.h"
namespace linkerhand {
namespace hand {
/**
* @brief Modbus L10 型号灵巧手实现类
*
* 提供通过 Modbus 协议通信的 L10 型号功能实现
*/
class ModbusL10Hand : public IHand
{
public:
LinkerHand(uint32_t handId);
~LinkerHand();
// 设置关节位置
void setJointPositions(const std::vector<uint8_t> &jointAngles) override;
void setJointPositionArc(const std::vector<double> &jointAngles) override;
// 设置最大扭矩
void setTorque(const std::vector<uint8_t> &torque) override;
// 设置关节速度
void setSpeed(const std::vector<uint8_t> &speed) override;
// 获取当前速度
std::vector<uint8_t> getSpeed() override;
// 获取当前扭矩
std::vector<uint8_t> getTorque() override;
// 获取当前关节状态
std::vector<uint8_t> getCurrentStatus() override;
std::vector<double> getCurrentStatusArc() override;
private:
uint32_t handId;
std::thread receiveThread;
Communication::ModBus bus;
bool running;
void receiveResponse();
std::vector<uint8_t> joints;
std::vector<uint8_t> speed;
std::vector<uint8_t> torque;
};
} // namespace hand
} // namespace linkerhand
// 向后兼容:在旧命名空间中提供别名
namespace ModbusLinkerHandL10 {
using LinkerHand = linkerhand::hand::ModbusL10Hand;
} // namespace ModbusLinkerHandL10
#endif
#endif // LINKERHAND_MODBUS_L10_H
#ifdef _WIN32
#ifndef LINKERHAND_PCAN_BUS_H
#define LINKERHAND_PCAN_BUS_H
#include <iostream>
#include <windows.h>
#include <PCANBasic.h>
#include <vector>
#include <mutex>
#include <atomic>
#include <chrono>
#include <thread>
#include <queue>
#include <sstream>
#include <iomanip>
#include <thread>
#include "ICanBus.h"
namespace linkerhand {
namespace communication {
/**
* @brief Windows PCAN 总线实现
*/
class PCANBus : public ICanBus
{
public:
PCANBus(TPCANHandle channel, TPCANBaudrate bitrate, const LINKER_HAND linkerhand);
~PCANBus();
std::string printMillisecondTime();
void send(const std::vector<uint8_t>& data, uint32_t can_id, const bool wait = false) override;
CANFrame recv(uint32_t &id) override;
void updateSendRate();
void updateReceiveRate();
private:
TPCANHandle channel;
LINKER_HAND linker_hand;
std::mutex mutex_send;
std::mutex mutex_receive;
std::mutex send_mutex;
std::atomic<int> send_count;
std::chrono::steady_clock::time_point last_time;
std::mutex receive_mutex;
std::atomic<int> receive_count;
std::chrono::steady_clock::time_point receive_last_time;
std::queue<TPCANMsg> send_queue;
};
} // namespace communication
} // namespace linkerhand
// 向后兼容:在全局命名空间中提供别名
namespace Communication = linkerhand::communication;
#endif // LINKERHAND_PCAN_BUS_H
#endif
#ifndef LINKERHAND_RANGE_TO_ARC_H
#define LINKERHAND_RANGE_TO_ARC_H
#include <vector>
#include <stdexcept>
#include <cmath>
#include <condition_variable>
//---------------------------------------------------------------------------------------------------
// L7 L OK
const std::vector<double> l7_l_min = {0, 0, 0, 0, 0, 0, -0.52};
const std::vector<double> l7_l_max = {0.44, 1.43, 1.62, 1.62, 1.62, 1.62, 1.01};
const std::vector<int> l7_l_derict = {-1, -1, -1, -1, -1, -1, -1};
// L7 R OK (urdf后续会更改!!!)
const std::vector<double> l7_r_min = {0, -1.43, 0, 0, 0, 0, 0};
const std::vector<double> l7_r_max = {0.75, 0, 1.62, 1.62, 1.62, 1.62, 1.54};
const std::vector<int> l7_r_derict = {-1, 0, -1, -1, -1, -1, -1};
//---------------------------------------------------------------------------------------------------
// L10 L OK
const std::vector<double> l10_l_min = {0, 0, 0, 0, 0, 0, 0, -0.26, -0.26, -0.52};
const std::vector<double> l10_l_max = {1.45, 1.43, 1.62, 1.62, 1.62, 1.62, 0.26, 0, 0, 1.01};
const std::vector<int> l10_l_derict = {-1, -1, -1, -1, -1, -1, 0, -1, -1, -1};
// L10 R OK
const std::vector<double> l10_r_min = {0, 0, 0, 0, 0, 0, -0.26, 0, 0, -0.52};
const std::vector<double> l10_r_max = {0.75, 1.43, 1.62, 1.62, 1.62, 1.62, 0, 0.13, 0.26, 1.01};
const std::vector<int> l10_r_derict = {-1, -1, -1, -1, -1, -1, -1, 0, 0, -1};
//---------------------------------------------------------------------------------------------------
// L20 L OK
const std::vector<double> l20_l_min = {0, 0, 0, 0, 0, -0.297, -0.26, -0.26, -0.26, -0.26, 0.122, 0, 0, 0, 0, 0, 0, 0, 0, 0};
const std::vector<double> l20_l_max = {0.87, 1.4, 1.4, 1.4, 1.4, 0.683, 0.26, 0.26, 0.26, 0.26, 1.78, 0, 0, 0, 0, 1.29, 1.08, 1.08, 1.08, 1.08};
const std::vector<int> l20_l_derict = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, -1, -1, -1, -1, -1};
// L20 R OK
const std::vector<double> l20_r_min = {0, 0, 0, 0, 0, -0.297, -0.26, -0.26, -0.26, -0.26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
const std::vector<double> l20_r_max = {0.87, 1.4, 1.4, 1.4, 1.4, 0.683, 0.26, 0.26, 0.26, 0.26, 1.78, 0, 0, 0, 0, 1.29, 1.08, 1.08, 1.08, 1.08};
const std::vector<int> l20_r_derict = {-1, -1, -1, -1, -1, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, -1, -1, -1, -1};
//---------------------------------------------------------------------------------------------------
// L21 L OK
const std::vector<double> l21_l_min = {0, 0, 0, 0, 0, 0, 0, -0.18, -0.18, 0, -0.6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
const std::vector<double> l21_l_max = {1, 1.57, 1.57, 1.57, 1.57, 1.6, 0.18, 0.18, 0.18, 0.18, 0.6, 0, 0, 0, 0, 1.57, 0, 0, 0, 0, 1.57, 1.57, 1.57, 1.57, 1.57};
const std::vector<int> l21_l_derict = {-1, -1, -1, -1, -1, -1, -1, -1, -1, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, -1, -1, -1, -1};
// L21 R OK
const std::vector<double> l21_r_min = {0, 0, 0, 0, 0, 0, -0.18, -0.18, -0.18, -0.18, -0.6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
const std::vector<double> l21_r_max = {1, 1.57, 1.57, 1.57, 1.57, 1.6, 0.18, 0.18, 0.18, 0.18, 0.6, 0, 0, 0, 0, 1.57, 0, 0, 0, 0, 1.57, 1.57, 1.57, 1.57, 1.57};
const std::vector<int> l21_r_derict = {-1, -1, -1, -1, -1, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, -1, -1, -1, -1};
//---------------------------------------------------------------------------------------------------
// L25 L OK
const std::vector<double> l25_l_min = {0, 0, 0, 0, 0, 0, -0.26, -0.26, -0.26, -0.26, -0.26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
const std::vector<double> l25_l_max = {0.9, 1.57, 1.57, 1.57, 1.57, 1.3, 0.26, 0.26, 0.26, 0.26, 0.61, 0, 0, 0, 0, 1.57, 1.57, 1.57, 1.57, 1.57, 1.57, 1.57, 1.57, 1.57, 1.57};
const std::vector<int> l25_l_derict = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
// L25 R OK
const std::vector<double> l25_r_min = {0, 0, 0, 0, 0, 0, -0.26, -0.26, -0.26, -0.26, -0.26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
const std::vector<double> l25_r_max = {0.9, 1.57, 1.57, 1.57, 1.57, 1.3, 0.26, 0.26, 0.26, 0.26, 0.61, 0, 0, 0, 0, 1.57, 1.57, 1.57, 1.57, 1.57, 1.57, 1.57, 1.57, 1.57, 1.57};
const std::vector<int> l25_r_derict = {-1, -1, -1, -1, -1, -1, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
//---------------------------------------------------------------------------------------------------
double is_within_range(double value, double min_value, double max_value);
double scale_value(double original_value, double a_min, double a_max, double b_min, double b_max);
bool should_skip_joint(int joints_type, int joint_index);
bool initialize_params(int joints_type, const std::string& left_or_right, std::vector<double>& min_limits, std::vector<double>& max_limits, std::vector<int>& derict);
std::vector<double> range_to_arc(const int &joints_type, const std::string &left_or_right, const std::vector<uint8_t> &hand_range);
std::vector<uint8_t> arc_to_range(const int &joints_type, const std::string &left_or_right, const std::vector<double> &hand_arc);
#endif // LINKERHAND_RANGE_TO_ARC_H
This diff was suppressed by a .gitattributes entry.
liblinkerhand_cpp_sdk.so.1.5.8
\ No newline at end of file
This diff was suppressed by a .gitattributes entry.
liblinkerhand_cpp_sdk.so.1.5.8
\ No newline at end of file
#!/bin/bash
# 从 CMakeLists.txt 读取版本号
VERSION=$(grep -E "^\s*VERSION\s+" CMakeLists.txt | head -1 | sed 's/.*VERSION\s\+\([0-9.]*\).*/\1/')
if [ -z "$VERSION" ]; then
# 如果无法读取,使用默认版本
VERSION="1.1.7"
fi
# 确定系统架构
ARCH=$(uname -m)
# 设置安装目录
INSTALL_PREFIX="/usr/local"
function LoadedColor_information()
{
# 文本颜色
BLACK='\033[0;30m'
# 黑色
RED='\033[0;31m'
# 红色
GREEN='\033[0;32m'
# 绿色
YELLOW='\033[0;33m'
# 黄色
BLUE='\033[0;34m'
# 蓝色
PURPLE='\033[0;35m'
# 紫色
CYAN='\033[0;36m'
# 青色
WHITE='\033[0;37m'
# 白色
RED_WHITE='\033[47;31m'
# 红色
GREEN_WHITE='\033[47;32m'
# 绿色
WHITE_BLACK='\033[40;37m'
# 白色+黑色
YELLOW_WHITE='\033[47;33m'
# 黄色+白色
# 背景颜色
BLACK_B='\033[40m'
# 黑色
RED_B='\033[41m'
# 红色
GREEN_B='\033[42m'
# 绿色
yellow_B='\033[43m'
# 黄色
blue_B='\033[44m'
# 蓝色
purple_B='\033[45m'
# 紫色
Cyan_B='\033[46m'
# 青色
White_B='\033[47m'
# 白色
# 文本样式
RESET='\033[0m'
# 重置所有属性
BOLD='\033[1m'
# 粗体
underline='\033[4m'
# 下划线
Blinking='\033[5m'
# 闪烁
Invert='\033[7m'
# 反显
Hidden='\033[8m'
# 隐藏
# echo "========================================================="
# echo -e "${GREEN}Successfully loaded color information !${RESET}"
# echo "========================================================="
}
CLOSE_RESERVE="exce bash" # exce bash
RUN() {
local launch=$1
gnome-terminal -- bash -c "source devel/setup.sh; $launch; $CLOSE_RESERVE"
sleep 3
}
RUN1() {
local launch=$1
local command package launch_file
IFS=' ' read -r command package launch_file <<< "$launch"
gnome-terminal -- bash -c "source devel/setup.sh; $launch; $CLOSE_RESERVE" > /dev/null 2>&1
echo "Node: $package"
while ! rosnode list | grep -q $package; do
echo -e " starting..."
sleep 1
done
echo -e " Startup completed"
}
function build_sdk(){
mkdir build; cd build; cmake ..; make; cd ..
}
function install_sdk(){
# 安装脚本
set -e
echo "检测到系统架构: $ARCH"
echo "正在安装 linkerhand-cpp-sdk 到系统..."
# 安装头文件
echo "安装头文件到 $INSTALL_PREFIX/include/linkerhand-cpp-sdk..."
sudo mkdir -p $INSTALL_PREFIX/include/linkerhand-cpp-sdk
sudo cp -r include/* $INSTALL_PREFIX/include/linkerhand-cpp-sdk/
# 安装库文件
if [ "$ARCH" = "x86_64" ]; then
echo "安装 x86_64 库文件..."
sudo mkdir -p $INSTALL_PREFIX/lib/linkerhand-cpp-sdk/x86_64
sudo cp -r lib/x86_64/* $INSTALL_PREFIX/lib/linkerhand-cpp-sdk/x86_64/
# 创建符号链接到标准库目录
for lib_file in lib/x86_64/*.so*; do
lib_name=$(basename $lib_file)
sudo ln -sf $INSTALL_PREFIX/lib/linkerhand-cpp-sdk/x86_64/$lib_name $INSTALL_PREFIX/lib/
done
elif [ "$ARCH" = "aarch64" ]; then
echo "安装 aarch64 库文件..."
sudo mkdir -p $INSTALL_PREFIX/lib/linkerhand-cpp-sdk/aarch64
sudo cp -r lib/aarch64/* $INSTALL_PREFIX/lib/linkerhand-cpp-sdk/aarch64/
# 创建符号链接到标准库目录
for lib_file in lib/aarch64/*.so*; do
lib_name=$(basename $lib_file)
sudo ln -sf $INSTALL_PREFIX/lib/linkerhand-cpp-sdk/aarch64/$lib_name $INSTALL_PREFIX/lib/
done
else
echo "不支持的架构: $ARCH"
exit 1
fi
# 更新动态链接库缓存
echo "更新动态链接库缓存..."
sudo ldconfig
echo "安装完成!"
echo ""
echo "使用示例:"
echo " 编译: g++ -o test test.cpp -llinkerhand_cpp_sdk -I/usr/local/include/linkerhand-cpp-sdk"
echo " 运行: LD_LIBRARY_PATH=/usr/local/lib ./test"
}
function uninstall_sdk(){
sudo rm -rf $INSTALL_PREFIX/lib/linkerhand-cpp-sdk
sudo rm -rf $INSTALL_PREFIX/include/linkerhand-cpp-sdk
sudo rm -rf $INSTALL_PREFIX/lib/liblinkerhand_cpp_sdk.so*
}
function run_example(){
cd build; ./toolset_example
}
#------------------------------------------------ Select Menu ------------------------------------------
function select_menu(){
cd $current_dir
echo -e "${GREEN}Please enter options: ${RESET}"
read -p "" select_num
case $select_num in
1)
echo "Build SDK"
build_sdk
;;
2)
echo "Install SDK"
build_sdk
install_sdk
;;
3)
echo "Uninstall SDK"
uninstall_sdk
;;
6)
echo "Execution Example"
run_example
;;
0)
echo "Exit"
exit
;;
*)
echo -e "${RED}Input error, please re-enter!${RESET}"
sleep 1
;;
esac
}
#------------------------------------------------ Menu ------------------------------------------
function show_Info(){
echo -e "${YELLOW}"
echo "================================================"
echo -e "${YELLOW} LinkerHand CPP-SDK Version:${VERSION} ${RESET}"
echo -e "${YELLOW}================================================${RESET}"
echo -e "${GREEN}"
echo "RUN Choose Task:"
echo -e "${YELLOW}————————————————————————————————————————————————${RESET}"
echo -e "${BLUE}[1]: Build SDK${RESET}"
echo -e "${YELLOW}————————————————————————————————————————————————${RESET}"
echo -e "${BLUE}[2]: Install SDK${RESET}"
echo -e "${YELLOW}————————————————————————————————————————————————${RESET}"
echo -e "${BLUE}[3]: Uninstall SDK${RESET}"
echo -e "${YELLOW}————————————————————————————————————————————————${RESET}"
echo -e "${RED}[0]: Exit${RESET}"
echo -e "${YELLOW}————————————————————————————————————————————————${RESET}"
# sudo make DESTDIR=/home/lst/Desktop/install install
}
#------------------------------------------------ Init ------------------------------------------
function Init()
{
LoadedColor_information
current_dir=$(pwd)
sleep 1
}
#------------------------------------------------ Main ------------------------------------------
Init
while true
do
show_Info
select_menu
done
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment