70 Commits

Author SHA1 Message Date
NuoDaJia02
12cebcb98b force update status
after control
2026-01-20 15:17:14 +08:00
NuoDaJia02
e307fa2ad3 add gripper status 2026-01-20 10:32:56 +08:00
NuoDaJia02
2adc702a59 完善夹爪状态判断逻辑 2026-01-13 11:52:28 +08:00
NuoDaJia02
c503bc42db 优化gripper代码 2026-01-13 11:12:30 +08:00
NuoDaJia02
ccca72679f update gripper launch 2026-01-09 16:56:15 +08:00
NuoDaJia02
be73f4ee83 update img_dev 2026-01-09 14:13:55 +08:00
NuoDaJia02
999fb4dac1 change gripper uart 2026-01-09 09:55:19 +08:00
NuoDaJia02
307094ffed 修改夹爪launch文件,支持双夹爪 2026-01-07 10:08:16 +08:00
NuoDaJia02
83caf55dbe update drivers @huiyu 2026-01-04 17:53:35 +08:00
NuoDaJia02
1a6fe5df70 update img dev config 2025-12-28 17:16:26 +08:00
NuoDaJia02
afa2b443f5 ethercat for R1 robot 2025-12-23 15:18:11 +08:00
NuoDaJia02
199d10da6d merge master 2025-12-18 16:32:08 +08:00
hehe
874bfaaf8d ethercat driver finish 0 2025-12-16 14:24:53 +08:00
hehe
51dc86ea4a add ecrt_driver 2025-12-12 18:52:52 +08:00
2c44401bc6 merge remote 2025-11-27 10:50:40 +08:00
c0f01c4b30 modify jz -> gripper 2025-11-27 10:48:44 +08:00
hehe
78c8894335 can speed mode 2025-11-25 18:14:00 +08:00
hehe
6b6552ebea add log 2025-11-25 17:50:40 +08:00
hehe
5a8391f784 jz double node 2025-11-25 17:34:16 +08:00
NuoDaJia02
ee826f8042 del main.cpp 2025-11-25 15:38:37 +08:00
hehe
db2f338fb8 fix bug 2025-11-25 15:14:41 +08:00
hehe
9ff3b2b6b6 add jz 2025-11-25 11:41:22 +08:00
hehe
a03bb14e1e add meta_dev 2025-11-25 11:05:03 +08:00
e5cc743baf fix bug 2025-11-21 16:28:00 +08:00
NuoDaJia02
fac220ccf4 update drivers 2025-11-21 14:14:24 +08:00
7025a33871 close service, add logs 2025-11-21 14:11:29 +08:00
david
0b83c96472 更新jz_dev 2025-11-21 10:09:03 +08:00
8fd31fb584 测试视觉抓取,修改部分配置 2025-11-21 09:38:39 +08:00
NuoDaJia02
080af2f7fa code optimization 2025-11-02 15:42:32 +08:00
NuoDaJia02
986004aaa4 del msg 2025-10-30 18:36:15 +08:00
NuoDaJia02
9f64856540 del msg 2025-10-30 18:35:57 +08:00
NuoDaJia02
67615b7e9d code update 2025-10-29 17:00:27 +08:00
NuoDaJia02
38a4c4a193 change speed to angle 2025-10-29 15:25:37 +08:00
hehe
862d685e06 add jz_dev 2025-10-29 11:55:43 +08:00
NuoDaJia02
81ec425d21 change speed mode 2025-10-28 18:35:21 +08:00
NuoDaJia02
28cd3f03fa del msg 2025-10-28 16:02:52 +08:00
NuoDaJia02
9acd27ee40 one orbbec and one realsense 2025-10-28 16:00:42 +08:00
NuoDaJia02
a535062a65 switch interfaces imu_msg 2025-10-28 15:54:36 +08:00
NuoDaJia02
6681ebff6e add svc to set param 2025-10-28 15:54:03 +08:00
NuoDaJia02
382be86637 add off function 2025-10-27 14:31:16 +08:00
NuoDaJia02
071aec3b6c 提高LED灯响应速度 2025-10-27 11:42:19 +08:00
NuoDaJia02
4c94eb2805 提高LED灯响应速度 2025-10-27 11:28:32 +08:00
NuoDaJia02
8d690aee80 led_dev add thread 2025-10-24 19:42:02 +08:00
NuoDaJia02
bfe060e01a add dev_test 2025-10-24 10:40:42 +08:00
NuoDaJia02
d8b03d8ad1 change msg to interfaces 2025-10-23 17:42:54 +08:00
hehe
ece5a41a6d add led dev 2025-10-23 11:54:24 +08:00
hivecore
f55b8f6dfd modify axis number 2025-10-23 09:01:47 +08:00
NuoDaJia02
dd522ad1e8 Merge branch 'main' of ssh://192.168.10.50:2222/HiveCoreRD/hivecore_robot_drivers 2025-10-22 20:50:27 +08:00
NuoDaJia02
59eac59814 motor param 2025-10-22 20:49:55 +08:00
hehe
18412436cc Merge branch 'main' of ssh://192.168.10.50:2222/HiveCoreRD/hivecore_robot_drivers 2025-10-22 12:46:15 +08:00
hehe
c3be6f1093 del } 2025-10-22 12:45:13 +08:00
NuoDaJia02
28f9f7edd7 modify ethercat 2025-10-21 17:49:44 +08:00
NuoDaJia02
295d77701b motor pub pos 2025-10-21 15:22:01 +08:00
NuoDaJia02
eda6722b1d modify camara profile 2025-10-21 08:45:41 +08:00
NuoDaJia02
733c0258f8 del log 2025-10-20 20:11:09 +08:00
NuoDaJia02
2edd72e610 Merge branch 'main' of ssh://192.168.10.50:2222/HiveCoreRD/hivecore_robot_drivers 2025-10-20 20:09:48 +08:00
NuoDaJia02
efd49b99cd ben mo ok 2025-10-20 20:07:29 +08:00
NuoDaJia
afbc867b05 comment out log 2025-10-19 21:38:51 +08:00
NuoDaJia
8a0a24099b fix ethercat issues 2025-10-19 17:24:17 +08:00
NuoDaJia
110b253c0c update gitignore 2025-10-18 16:59:37 +08:00
NuoDaJia
753a0baa9a Merge branch 'main' of ssh://192.168.10.50:2222/HiveCoreRD/hivecore_robot_drivers 2025-10-18 16:26:22 +08:00
NuoDaJia
492edcbf64 fix enable problem 2025-10-18 16:24:58 +08:00
hehe
75150a4a49 add mt motor 2025-10-16 19:21:20 +08:00
NuoDaJia02
99d6f87a4b tmp 2025-10-16 17:38:20 +08:00
hehe
1a370a796d motor_dev 2025-10-16 10:05:13 +08:00
NuoDaJia02
dedc31a46e adapte robot 2025-10-16 09:01:58 +08:00
NuoDaJia02
89b839c2cc Merge branch 'main' of ssh://192.168.10.50:2222/HiveCoreRD/hivecore_robot_drivers 2025-10-16 08:59:48 +08:00
NuoDaJia02
fef088ac3a adapter robot 2025-10-16 08:56:43 +08:00
NuoDaJia
a64aa096e3 add real ethercat driver 2025-10-15 20:56:44 +08:00
NuoDaJia
ab5b86b04d add ethercat drive for mt motor and zerrorErr motor 2025-10-15 20:55:53 +08:00
3575 changed files with 840579 additions and 145 deletions

12
.gitignore vendored Normal file
View File

@@ -0,0 +1,12 @@
# 忽略所有层级的 build 文件夹及其内容
**/build/
# 忽略所有层级的 install 文件夹及其内容
**/install/
# 忽略所有层级的 log 文件夹及其内容
**/log/
ethercat_dev/csp_log.csv
csp_log.csv

18
.vscode/c_cpp_properties.json vendored Normal file
View File

@@ -0,0 +1,18 @@
{
"configurations": [
{
"name": "linux-gcc-x64",
"includePath": [
"${workspaceFolder}/**"
],
"compilerPath": "/usr/bin/gcc",
"cStandard": "${default}",
"cppStandard": "${default}",
"intelliSenseMode": "linux-gcc-x64",
"compilerArgs": [
""
]
}
],
"version": 4
}

24
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,24 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "C/C++ Runner: Debug Session",
"type": "cppdbg",
"request": "launch",
"args": [],
"stopAtEntry": false,
"externalConsole": false,
"cwd": "/home/demo/hivecore_robot_os1/hivecore_robot_drivers/motor_dev/src",
"program": "/home/demo/hivecore_robot_os1/hivecore_robot_drivers/motor_dev/src/build/Debug/outDebug",
"MIMode": "gdb",
"miDebuggerPath": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}

136
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,136 @@
{
"files.associations": {
"cctype": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"csignal": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"any": "cpp",
"array": "cpp",
"atomic": "cpp",
"bit": "cpp",
"bitset": "cpp",
"chrono": "cpp",
"codecvt": "cpp",
"compare": "cpp",
"complex": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"cstdint": "cpp",
"deque": "cpp",
"forward_list": "cpp",
"list": "cpp",
"map": "cpp",
"set": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"vector": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"regex": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"fstream": "cpp",
"future": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"numbers": "cpp",
"ostream": "cpp",
"semaphore": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"thread": "cpp",
"cfenv": "cpp",
"cinttypes": "cpp",
"typeindex": "cpp",
"typeinfo": "cpp",
"valarray": "cpp",
"variant": "cpp",
"filesystem": "cpp"
},
"C_Cpp_Runner.cCompilerPath": "gcc",
"C_Cpp_Runner.cppCompilerPath": "g++",
"C_Cpp_Runner.debuggerPath": "gdb",
"C_Cpp_Runner.cStandard": "",
"C_Cpp_Runner.cppStandard": "",
"C_Cpp_Runner.msvcBatchPath": "",
"C_Cpp_Runner.useMsvc": false,
"C_Cpp_Runner.warnings": [
"-Wall",
"-Wextra",
"-Wpedantic",
"-Wshadow",
"-Wformat=2",
"-Wcast-align",
"-Wconversion",
"-Wsign-conversion",
"-Wnull-dereference"
],
"C_Cpp_Runner.msvcWarnings": [
"/W4",
"/permissive-",
"/w14242",
"/w14287",
"/w14296",
"/w14311",
"/w14826",
"/w44062",
"/w44242",
"/w14905",
"/w14906",
"/w14263",
"/w44265",
"/w14928"
],
"C_Cpp_Runner.enableWarnings": true,
"C_Cpp_Runner.warningsAsError": false,
"C_Cpp_Runner.compilerArgs": [],
"C_Cpp_Runner.linkerArgs": [],
"C_Cpp_Runner.includePaths": [],
"C_Cpp_Runner.includeSearch": [
"*",
"**/*"
],
"C_Cpp_Runner.excludeSearch": [
"**/build",
"**/build/**",
"**/.*",
"**/.*/**",
"**/.vscode",
"**/.vscode/**"
],
"C_Cpp_Runner.useAddressSanitizer": false,
"C_Cpp_Runner.useUndefinedSanitizer": false,
"C_Cpp_Runner.useLeakSanitizer": false,
"C_Cpp_Runner.showCompilationTime": false,
"C_Cpp_Runner.useLinkTimeOptimization": false,
"C_Cpp_Runner.msvcSecureNoWarnings": false,
"Codegeex.RepoIndex": true
}

File diff suppressed because one or more lines are too long

123
aikit_tts/CMakeLists.txt Normal file
View File

@@ -0,0 +1,123 @@
cmake_minimum_required(VERSION 3.8)
project(aikit_tts)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# 查找依赖
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
# 科大讯飞SDK路径配置 - 关键修改
set(XUNFEI_SDK_LIB_DIRS
${CMAKE_CURRENT_SOURCE_DIR}/libs
)
# -------------------------- 新增:资源路径配置 --------------------------
# 1. 定义资源源路径相对当前CMakeLists.txt的路径即resources/tts
set(TTS_RESOURCE_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/resources/tts)
# 2. 定义资源安装路径ROS 2标准路径share/包名/tts
# 安装后实际路径:/opt/ros/humble/share/aikit_tts/tts/
# 显式指定资源安装路径避免CMAKE_INSTALL_DATADIR解析异常
set(TTS_RESOURCE_INSTALL_DIR share/${PROJECT_NAME}/tts)
# -----------------------------------------------------------------------
# 添加头文件路径
include_directories(
include
${CMAKE_CURRENT_SOURCE_DIR}/include # 项目自身头文件
)
# 添加库文件路径
link_directories(
${XUNFEI_SDK_LIB_DIRS}
)
# 打印库路径用于调试
foreach(LIB_DIR ${XUNFEI_SDK_LIB_DIRS})
if(EXISTS "${LIB_DIR}/ebd1bade4_v1025_aee.so")
message(STATUS "找到ebd1bade4_v1025_aee.so: ${LIB_DIR}/ebd1bade4_v1025_aee.so")
endif()
if(EXISTS "${LIB_DIR}/libaikit.so")
message(STATUS "找到libaikit.so: ${LIB_DIR}/libaikit.so")
endif()
endforeach()
# 编译节点
add_executable(aikit_tts_node
src/aikit_tts_node.cpp
)
# 依赖项链接
ament_target_dependencies(aikit_tts_node
rclcpp
std_msgs
)
# -------------------------- 新增:传递资源路径给程序 --------------------------
# 通过宏定义TTS_RESOURCE_PATH把安装路径传给cpp代码末尾加/方便拼接文件名)
target_compile_definitions(aikit_tts_node
PRIVATE
TTS_RESOURCE_PATH="${TTS_RESOURCE_INSTALL_DIR}/"
)
# -----------------------------------------------------------------------
# 链接库
target_link_libraries(aikit_tts_node
-L${XUNFEI_SDK_LIB_DIRS} # 显式指定库路径
aikit # 科大讯飞主库
dl pthread m rt asound
)
# 安装可执行文件
install(TARGETS
aikit_tts_node
DESTINATION lib/${PROJECT_NAME}
)
# 安装SDK库文件到系统能找到的地方
install(DIRECTORY ${XUNFEI_SDK_LIB_DIRS}/
DESTINATION lib
FILES_MATCHING PATTERN "*.so*"
)
# -------------------------- 新增:安装资源文件 --------------------------
# 把resources/tts下的.dat和.irf文件安装到前面定义的TTS_RESOURCE_INSTALL_DIR
# 安装资源文件(修改后)
install(
DIRECTORY ${TTS_RESOURCE_SRC_DIR}/
# 拼接安装根目录,最终路径是 install/share/aikit_tts/tts/
DESTINATION ${CMAKE_INSTALL_PREFIX}/${TTS_RESOURCE_INSTALL_DIR}
FILES_MATCHING
PATTERN "*.dat"
PATTERN "*.irf"
)
# -----------------------------------------------------------------------
# 安装头文件
install(DIRECTORY include/
DESTINATION include
)
# 安装config和launch目录原有配置
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/config)
install(DIRECTORY config/
DESTINATION share/${PROJECT_NAME}/config
)
endif()
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/launch)
install(DIRECTORY launch/
DESTINATION share/${PROJECT_NAME}/launch
)
endif()
ament_export_include_directories(include)
ament_export_dependencies(rclcpp std_msgs)
ament_package()

View File

Binary file not shown.

View File

@@ -0,0 +1,337 @@
//
// Created by xkzhang9 on 2020/10/16.
//
#ifndef AIKIT_BIZ_API_H
#define AIKIT_BIZ_API_H
#include "aikit_biz_type.h"
#include "aikit_biz_builder.h"
namespace AIKIT {
/**
* SDK初始化函数用以初始化整个SDK
* 初始化相关配置参数通过AIKIT_Congigurator配置
* @return 结果错误码0=成功
*/
AIKITAPI int32_t AIKIT_Init();
/**[deprecated]
* SDK初始化函数用以初始化整个SDK
* @param param SDK配置参数
* @return 结果错误码0=成功
*/
AIKITAPI int32_t AIKIT_Init(AIKIT_InitParam* param);
/**
* SDK逆初始化函数用以释放SDK所占资源
* @return 结果错误码0=成功
*/
AIKITAPI int32_t AIKIT_UnInit();
/**
* 注册回调函数用以返回执行结果
* @param onOutput 能力实际输出回调
* @param onEvent 能力执行事件回调
* @param onError 能力执行错误回调
* @return 结果错误码0=成功
*/
AIKITAPI int32_t AIKIT_RegisterCallback(AIKIT_Callbacks cbs);
/**
* 注册回调函数用以返回执行结果
* @param ability [in] 能力唯一标识
* @param onOutput 能力实际输出回调
* @param onEvent 能力执行事件回调
* @param onError 能力执行错误回调
* @return 结果错误码0=成功
*/
AIKITAPI int32_t AIKIT_RegisterAbilityCallback(const char* ability, AIKIT_Callbacks cbs);
/**
* 初始化能力引擎
* @param ability [in] 能力唯一标识
* @param param [in] 初始化参数
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_EngineInit(const char* ability, AIKIT_BizParam* param);
/**
* 能力引擎逆初始化,释放能力及对应引擎占用所有资源
* @param ability [in] 能力唯一标识
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_EngineUnInit(const char* ability);
/**
* 个性化数据预处理
* @param ability [in] 能力唯一标识
* @param srcData [in] 原始数据输入
* @param data [out] 结果数据输出
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_PreProcess(const char* ability, AIKIT_CustomData* srcData, AIKIT_CustomData** data);
/**
* 离线个性化数据加载
* @param ability 能力唯一标识
* @param data 个性化数据
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_LoadData(const char* ability, AIKIT_CustomData* data);
/**
* 在线个性化数据上传
* @param ability 能力唯一标识
* @param params 个性化参数
* @param data 个性化数据
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_LoadDataAsync(const char* ability, AIKIT_BizParam* params, AIKIT_InputData* data, void* usrContext, AIKIT_HANDLE** outHandle);
/**
* 个性化数据查询
* @param ability 能力唯一标识
* @param data 个性化数据
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_QueryData(const char* ability, AIKIT_CustomData* data);
/**
* @brief 可见即可说AIUI定制接口
* @param ability 能力唯一标识
* @param params 个性化参数
* @param data 个性化数据
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_LoadDataSpeakableAsync(const char* ability, AIKIT_BizParam* params, AIKIT_InputData* data, void* usrContext, AIKIT_HANDLE** outHandle);
/**
* 个性化数据卸载
* @param ability 能力唯一标识
* @param key 个性化数据唯一标识
* @param index 个性化数据索引
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_UnLoadData(const char* ability, const char* key, int index);
/**
* 指定要使用的个性化数据集合未调用则默认使用所有AIKIT_LoadData加载的数据
* 可调用多次以使用不同key集合
* @param abilityId 能力唯一标识
* @param key 个性化数据唯一标识数组
* @param index 个性化数据索引数组
* @param count 个性化数据索引数组成员个数
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_SpecifyDataSet(const char* ability, const char* key, int index[], int count);
/**
* 启动one-shot模式能力同步模式调用
* @param ability 能力唯一标识
* @param param 能力参数
* @param inputData 能力数据输入
* @param outputData 能力数据输出
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_OneShot(const char* ability, AIKIT_BizParam* params, AIKIT_InputData* inputData, AIKIT_OutputData** outputData);
/**
* 启动one-shot模式能力异步模式调用
* @param ability 能力唯一标识d
* @param param 能力参数
* @param data 能力数据输入
* @param usrContext 上下文指针
* @param outHandle 生成的引擎会话句柄
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_OneShotAsync(const char* ability, AIKIT_BizParam* params, AIKIT_InputData* data, void* usrContext, AIKIT_HANDLE** outHandle);
/**
* 启动会话模式能力调用实例,若引擎未初始化,则内部首先完成引擎初始化
* @param ability 能力唯一标识
* @param len ability长度
* @param param 初始化参数
* @param usrContext上下文指针
* @param outHandle 生成的引擎会话句柄
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_Start(const char* ability, AIKIT_BizParam* param, void* usrContext, AIKIT_HANDLE** outHandle);
/**
* 会话模式输入数据
* @param handle 会话实例句柄
* @param input 输入数据
* @return 结果错误码0=成功
*/
AIKITAPI int32_t AIKIT_Write(AIKIT_HANDLE* handle, AIKIT_InputData* input);
/**
* 会话模式同步读取数据
* @param handle 会话实例句柄
* @param output 输入数据
* @return 结果错误码0=成功
* @note output内存由SDK自行维护,无需主动释放
*/
AIKITAPI int32_t AIKIT_Read(AIKIT_HANDLE* handle, AIKIT_OutputData** output);
/**
* 结束会话实例
* @param handle 会话实例句柄
* @return 结果错误码0=成功
*/
AIKITAPI int32_t AIKIT_End(AIKIT_HANDLE* handle);
/**
* 释放能力占用资源,注意不会释放引擎实例
* 若要释放能力及能力所有资源需调用AIKIT_EngineUnInit()
* @param ability 能力唯一标识
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_FreeAbility(const char* ability);
/**
* 设置日志级别
* @param level 日志级别
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_SetLogLevel(int32_t level);
/**
* 设置日志输出模式
* @param mode 日志输出模式
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_SetLogMode(int32_t mode);
/**
* 输出模式为文件时,设置日志文件名称
* @param path 日志名称
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_SetLogPath(const char* path);
/**
* 获取设备ID
* @param deviceID 设备指纹输出字符串
* @return 结果错误码0=成功
*/
AIKITAPI int32_t AIKIT_GetDeviceID(const char** deviceID);
/**
* 设置授权更新间隔单位为秒默认为300秒
* AIKIT_Init前设置
* @param interval 间隔秒数
* @return 结果错误码0=成功
*/
AIKITAPI int32_t AIKIT_SetAuthCheckInterval(uint32_t interval);
/**
* 强制更新授权
* 注意注意需在AIKIT_Init调用成功后方可调用
* @param timeout 超时时间 单位为秒
* @return 结果错误码0=成功
*/
AIKITAPI int32_t AIKIT_UpdateAuth(uint32_t timeout);
/**
* 获取能力授权剩余秒数
* 能力输入参数为空时,默认返回最接近授权过期的能力剩余秒数
* 注意注意需在AIKIT_Init调用成功后方可调用
* @param leftTime 返回的能力授权剩余秒数0 表示永久授权, 负值表示已过期的秒数
* @param ability 能力id标识
* @return 返回调用结果0 = 成功, 其他值表示调用失败
*/
AIKITAPI int AIKIT_GetAuthLeftTime(int64_t& leftTime,int64_t& authEndTime, const char* ability = nullptr);
/**
* 获取SDK版本号
* @return SDK版本号
*/
AIKITAPI const char* AIKIT_GetVersion();
/**
* @brief 获取能力对应的引擎版本
*
* @param ability 能力唯一标识
* @return const* 引擎版本号
*/
AIKITAPI const char* AIKIT_GetEngineVersion(const char* ability);
/**
* 本地日志是否开启
* @param open
* @return
*/
AIKITAPI int32_t AIKIT_SetILogOpen(bool open);
/**
* 本地日志最大存储个数【1300】
* @param count
* @return
*/
AIKITAPI int32_t AIKIT_SetILogMaxCount(uint32_t count);
/**
* 设置单日志文件大小((010M】
* @param bytes
* @return
*/
AIKITAPI int32_t AIKIT_SetILogMaxSize(long long bytes);
/**
* 设置SDK相关配置
* @param key 参数名字
* @param value 参数值
* @return 结果错误码0=成功
*/
AIKITAPI int32_t AIKIT_SetConfig(const char* key, const void* value);
/**
* 设置SDK内存模式
* @param ability 能力id
* @param mode 模式,取值参见 AIKIT_MEMORY_MODE
* @return AIKITAPI
*/
AIKITAPI int32_t AIKIT_SetMemoryMode(const char* ability,int32_t mode);
/**自2.1.6版本开始已废弃接口,当前仅保留空实现接口声明
* 自2.1.6版本开始AIKIT_OneShot响应数据由SDK内部自己维护无需用户维护
* 同步接口响应数据缓存释放接口
* @param outputData 由同步接口AIKIT_OneShot获取的响应结果数据
*/
AIKITAPI int32_t AIKIT_Free(AIKIT_OutputData** outputData);
/**自2.1.6版本开始已废弃接口由AIKIT_FreeAbility替代
* 释放能力占用资源
* @param ability 能力唯一标识
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_GC(const char* ability);
/**
* 设置日志级别,模式,以及保存路径,旧版日志接口,不推荐使用
* @param level 日志级别
* @param mode 日志输出模式
* @param level 输出模式为文件时的文件名称
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_SetLogInfo(int32_t level, int32_t mode, const char* path);
/**
* 获取能力授权剩余秒数
* 能力输入参数为空时,默认返回最接近授权过期的能力剩余秒数
* 注意注意需在AEE_Init调用成功后方可调用
* @param leftTime 返回的能力授权剩余秒数0 表示永久授权, 负值表示已过期的秒数
* @param ability 能力id标识
* @return 返回调用结果0 = 成功, 其他值表示调用失败
*/
AIKITAPI int AIKIT_GetAuthLeftTime(int64_t& leftTime, const char* ability = nullptr);
} // namespace AIKIT
#endif //AIKIT_BIZ_API_H

View File

@@ -0,0 +1,437 @@
//
// Created by chaoxu8 on 2021/07/01.
//
#ifndef AIKIT_BIZ_API_C_H
#define AIKIT_BIZ_API_C_H
#ifndef __cplusplus
#include <stdbool.h>
#endif
#include "aikit_biz_type.h"
#include "../api_aee/aee_biz_api_c.h"
#ifndef AEE_BIZ_API_C_H
// 构造器类型
typedef enum BuilderType_ {
BUILDER_TYPE_PARAM, // 参数输入
BUILDER_TYPE_DATA // 数据输入
} BuilderType;
// 构造器输入数据类型
typedef enum BuilderDataType_ {
DATA_TYPE_TEXT, // 文本
DATA_TYPE_AUDIO, // 音频
DATA_TYPE_IMAGE, // 图片
DATA_TYPE_VIDEO // 视频
} BuilderDataType;
// 构造器输入数据结构体
typedef struct BuilderData_ {
int type; // 数据类型
const char* name; // 数据段名
void* data; // 数据段实体(当送入路径时,此处传入路径地址字符串指针即可;
// 当送入文件句柄时,此处传入文件句柄指针即可)
int len; // 数据段长度当送入路径或文件句柄时此处传0即可
int status; // 数据段状态参考AIKIT_DataStatus枚举
} BuilderData;
#endif
// 构造器上下文及句柄
typedef struct AIKITBuilderContext_ {
void* builderInst; // 构造器内部类句柄
BuilderType type; // 构造器类型
} AIKITBuilderContext, *AIKITBuilderHandle;
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief 初始化输入构造器
* @param type 构造器类型
* @return 构造器句柄失败返回nullptr
*/
AIKITAPI AIKITBuilderHandle AIKITBuilder_Create(BuilderType type);
typedef AIKITBuilderHandle(*AIKITBuilder_Create_Ptr)(BuilderType type);
/**
* @brief 添加整型参数
* @param handle 构造器句柄
* @param name 参数名称
* @param value 参数值
* @return 结果错误码0=成功
*/
AIKITAPI int AIKITBuilder_AddInt(AIKITBuilderHandle handle, const char *key, int value);
typedef int(*AIKITBuilder_AddInt_Ptr)(AIKITBuilderHandle handle, const char *key, int value);
/**
* @brief 添加字符串型参数
* @param handle 构造器句柄
* @param name 参数名称
* @param str 参数值
* @param len 字符串长度
* @return 结果错误码0=成功
*/
AIKITAPI int AIKITBuilder_AddString(AIKITBuilderHandle handle, const char *key, const char *value, int len);
typedef int(*AIKITBuilder_AddString_Ptr)(AIKITBuilderHandle handle, const char *key, const char *value, int len);
/**
* @brief 添加布尔型参数
* @param handle 构造器句柄
* @param name 参数名称
* @param value 参数值
* @return 结果错误码0=成功
*/
AIKITAPI int AIKITBuilder_AddBool(AIKITBuilderHandle handle, const char *key, bool value);
typedef int(*AIKITBuilder_AddBool_Ptr)(AIKITBuilderHandle handle, const char *key, bool value);
/**
* @brief 添加浮点型参数
* @param handle 构造器句柄
* @param name 参数名称
* @param value 参数值
* @return 结果错误码0=成功
*/
AIKITAPI int AIKITBuilder_AddDouble(AIKITBuilderHandle handle, const char *key, double value);
typedef int(*AIKITBuilder_AddDouble_Ptr)(AIKITBuilderHandle handle, const char *key, double value);
/**
* @brief 添加输入数据
* @param handle 构造器句柄
* @param data 数据结构体实例
* @return 结果错误码0=成功
*/
AIKITAPI int AIKITBuilder_AddBuf(AIKITBuilderHandle handle, BuilderData *data);
typedef int(*AIKITBuilder_AddBuf_Ptr)(AIKITBuilderHandle handle, BuilderData *data);
/**
* @brief 添加输入数据(以路径方式)
* @param handle 构造器句柄
* @param data 数据结构体实例
* @return 结果错误码0=成功
*/
AIKITAPI int AIKITBuilder_AddPath(AIKITBuilderHandle handle, BuilderData *data);
typedef int(*AIKITBuilder_AddPath_Ptr)(AIKITBuilderHandle handle, BuilderData *data);
/**
* @brief 添加输入数据(以文件对象方式)
* @param handle 构造器句柄
* @param data 数据结构体实例
* @return 结果错误码0=成功
*/
AIKITAPI int AIKITBuilder_AddFile(AIKITBuilderHandle handle, BuilderData *data);
typedef int(*AIKITBuilder_AddFile_Ptr)(AIKITBuilderHandle handle, BuilderData *data);
/**
* @brief 构建输入参数
* @param handle 构造器句柄
* @return 参数结构化指针失败返回nullptr
*/
AIKITAPI AIKIT_BizParam* AIKITBuilder_BuildParam(AIKITBuilderHandle handle);
typedef AIKIT_BizParam*(*AIKITBuilder_BuildParam_Ptr)(AIKITBuilderHandle handle);
/**
* @brief 构建输入数据
* @param handle 构造器句柄
* @return 数据结构化指针失败返回nullptr
*/
AIKITAPI AIKIT_InputData* AIKITBuilder_BuildData(AIKITBuilderHandle handle);
typedef AIKIT_InputData*(*AIKITBuilder_BuildData_Ptr)(AIKITBuilderHandle handle);
/**
* @brief 清空输入构造器
* @param handle 构造器句柄
* @return 无
*/
AIKITAPI void AIKITBuilder_Clear(AIKITBuilderHandle handle);
typedef void(*AIKITBuilder_Clear_Ptr)(AIKITBuilderHandle handle);
/**
* @brief 销毁输入构造器
* @param handle 构造器句柄
* @return 无
*/
AIKITAPI void AIKITBuilder_Destroy(AIKITBuilderHandle handle);
typedef void(*AIKITBuilder_Destroy_Ptr)(AIKITBuilderHandle handle);
/**
* SDK初始化函数用以初始化整个SDK
* @param param SDK配置参数
* @return 结果错误码0=成功
*/
AIKITAPI int32_t AIKIT_Init(AIKIT_InitParam* param);
typedef int32_t(*AIKIT_Init_Ptr)(AIKIT_InitParam* param);
/**
* SDK逆初始化函数用以释放SDK所占资源
* @return 结果错误码0=成功
*/
AIKITAPI int32_t AIKIT_UnInit();
typedef int32_t(*AIKIT_UnInit_Ptr)();
/**
* 注册回调函数用以返回执行结果
* @param onOutput 能力实际输出回调
* @param onEvent 能力执行事件回调
* @param onError 能力执行错误回调
* @return 结果错误码0=成功
*/
AIKITAPI int32_t AIKIT_RegisterCallback(AIKIT_Callbacks cbs);
typedef int32_t(*AIKIT_RegisterCallback_Ptr)(AIKIT_Callbacks cbs);
/**
* 注册回调函数用以返回执行结果
* @param ability [in] 能力唯一标识
* @param onOutput 能力实际输出回调
* @param onEvent 能力执行事件回调
* @param onError 能力执行错误回调
* @return 结果错误码0=成功
*/
AIKITAPI int32_t AIKIT_RegisterAbilityCallback(const char* ability, AIKIT_Callbacks cbs);
typedef int32_t(*AIKIT_RegisterAbilityCallback_Ptr)(const char* ability, AIKIT_Callbacks cbs);
/**
* 初始化能力引擎
* @param ability [in] 能力唯一标识
* @param param [in] 初始化参数
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_EngineInit(const char* ability, AIKIT_BizParam* param);
typedef int32_t(*AIKIT_EngineInit_Ptr)(const char* ability, AIKIT_BizParam* param);
/**
* 能力引擎逆初始化
* @param ability [in] 能力唯一标识
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_EngineUnInit(const char* ability);
typedef int32_t(*AIKIT_EngineUnInit_Ptr)(const char* ability);
/**
* 个性化数据预处理
* @param ability [in] 能力唯一标识
* @param srcData [in] 原始数据输入
* @param data [out] 结果数据输出
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_PreProcess(const char* ability, AIKIT_CustomData* srcData, AIKIT_CustomData** data);
typedef int32_t(*AIKIT_PreProcess_Ptr)(const char* ability, AIKIT_CustomData* srcData, AIKIT_CustomData** data);
/**
* 个性化数据加载
* @param ability 能力唯一标识
* @param data 个性化数据
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_LoadData(const char* ability, AIKIT_CustomData* data);
typedef int32_t(*AIKIT_LoadData_Ptr)(const char* ability, AIKIT_CustomData* data);
/**
* 个性化数据加载
* @param ability 能力唯一标识
* @param key 个性化数据唯一标识
* @param index 个性化数据索引
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_UnLoadData(const char* ability, const char* key, int index);
typedef int32_t(*AIKIT_UnLoadData_Ptr)(const char* ability, const char* key, int index);
/**
* 指定要使用的个性化数据集合未调用则默认使用所有AIKIT_LoadData加载的数据
* 可调用多次以使用不同key集合
* @param abilityId 能力唯一标识
* @param key 个性化数据唯一标识数组
* @param index 个性化数据索引数组
* @param count 个性化数据索引数组成员个数
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_SpecifyDataSet(const char* ability, const char* key, int index[], int count);
typedef int32_t(*AIKIT_SpecifyDataSet_Ptr)(const char* ability, const char* key, int index[], int count);
/**
* 启动one-shot模式能力同步模式调用
* @param ability 能力唯一标识
* @param param 能力参数
* @param inputData 能力数据输入
* @param outputData 能力数据输出
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_OneShot(const char* ability, AIKIT_BizParam* params, AIKIT_InputData* inputData, AIKIT_OutputData** outputData);
typedef int32_t(*AIKIT_OneShot_Ptr)(const char* ability, AIKIT_BizParam* params, AIKIT_InputData* inputData, AIKIT_OutputData** outputData);
/**
* 启动one-shot模式能力异步模式调用
* @param ability 能力唯一标识d
* @param param 能力参数
* @param data 能力数据输入
* @param usrContext 上下文指针
* @param outHandle 生成的引擎会话句柄
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_OneShotAsync(const char* ability, AIKIT_BizParam* params, AIKIT_InputData* data, void* usrContext, AIKIT_HANDLE** outHandle);
typedef int32_t(*AIKIT_OneShotAsync_Ptr)(const char* ability, AIKIT_BizParam* params, AIKIT_InputData* data, void* usrContext, AIKIT_HANDLE** outHandle);
/**
* 启动会话模式能力调用实例,若引擎未初始化,则内部首先完成引擎初始化
* @param ability 能力唯一标识
* @param len ability长度
* @param param 初始化参数
* @param usrContext上下文指针
* @param outHandle 生成的引擎会话句柄
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_Start(const char* ability, AIKIT_BizParam* param, void* usrContext, AIKIT_HANDLE** outHandle);
typedef int32_t(*AIKIT_Start_Ptr)(const char* ability, AIKIT_BizParam* param, void* usrContext, AIKIT_HANDLE** outHandle);
/**
* 会话模式输入数据
* @param handle 会话实例句柄
* @param input 输入数据
* @return 结果错误码0=成功
*/
AIKITAPI int32_t AIKIT_Write(AIKIT_HANDLE* handle, AIKIT_InputData* input);
typedef int32_t(*AIKIT_Write_Ptr)(AIKIT_HANDLE* handle, AIKIT_InputData* input);
/**
* 会话模式同步读取数据
* @param handle 会话实例句柄
* @param output 输入数据
* @return 结果错误码0=成功
* @note output内存由SDK自行维护,无需主动释放
*/
AIKITAPI int32_t AIKIT_Read(AIKIT_HANDLE* handle, AIKIT_OutputData** output);
typedef int32_t(*AIKIT_Read_Ptr)(AIKIT_HANDLE* handle, AIKIT_OutputData** output);
/**
* 结束会话实例
* @param handle 会话实例句柄
* @return 结果错误码0=成功
*/
AIKITAPI int32_t AIKIT_End(AIKIT_HANDLE* handle);
typedef int32_t(*AIKIT_End_Ptr)(AIKIT_HANDLE* handle);
/**
* 释放能力占用资源
* @param ability 能力唯一标识
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_FreeAbility(const char* ability);
typedef int32_t(*AIKIT_FreeAbility_Ptr)(const char* ability);
/**
* 设置日志级别
* @param level 日志级别
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_SetLogLevel(int32_t level);
typedef int32_t(*AIKIT_SetLogLevel_Ptr)(int32_t level);
/**
* 设置日志输出模式
* @param mode 日志输出模式
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_SetLogMode(int32_t mode);
typedef int32_t(*AIKIT_SetLogMode_Ptr)(int32_t mode);
/**
* 输出模式为文件时,设置日志文件名称
* @param path 日志名称
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_SetLogPath(const char* path);
typedef int32_t(*AIKIT_SetLogPath_Ptr)(const char* path);
/**
* 获取设备ID
* @param deviceID 设备指纹输出字符串
* @return 结果错误码0=成功
*/
AIKITAPI int32_t AIKIT_GetDeviceID(const char** deviceID);
typedef int32_t(*AIKIT_GetDeviceID_Ptr)(const char** deviceID);
/**
* 设置授权更新间隔单位为秒默认为300秒
* AIKIT_Init前设置
* @param interval 间隔秒数
* @return 结果错误码0=成功
*/
AIKITAPI int32_t AIKIT_SetAuthCheckInterval(uint32_t interval);
typedef int32_t(*AIKIT_SetAuthCheckInterval_Ptr)(uint32_t interval);
/**
* 获取SDK版本号
* @return SDK版本号
*/
AIKITAPI const char* AIKIT_GetVersion();
typedef const char*(*AIKIT_GetVersion_Ptr)();
/**
* @brief 获取能力对应的引擎版本
*
* @param ability 能力唯一标识
* @return const* 引擎版本号
*/
AIKITAPI const char* AIKIT_GetEngineVersion(const char* ability);
typedef const char*(*AIKIT_GetEngineVersion_Ptr)(const char* ability);
/**
* 本地日志是否开启
* @param open
* @return
*/
AIKITAPI int32_t AIKIT_SetILogOpen(bool open);
typedef int32_t(*AIKIT_SetILogOpenPtr)(bool open);
/**
* 本地日志最大存储个数【1300】
* @param count
* @return
*/
AIKITAPI int32_t AIKIT_SetILogMaxCount(uint32_t count);
typedef int32_t(*AIKIT_SetILogMaxCountPtr)(uint32_t count);
/**
* 设置单日志文件大小((010M】
* @param bytes
* @return
*/
AIKITAPI int32_t AIKIT_SetILogMaxSize(long long bytes);
typedef int32_t(*AIKIT_SetILogMaxSizePtr)(long long bytes);
/**
* 设置SDK相关配置
* @param key 参数名字
* @param value 参数值
* @return 结果错误码0=成功
*/
AIKITAPI int32_t AIKIT_SetConfig(const char* key, const void* value);
typedef int32_t(*AIKIT_SetConfigPtr)(const char* key, const void* value);
/**
* 设置SDK内存模式
* @param ability 能力id
* @param mode 模式,取值参见 AIKIT_MEMORY_MODE
* @return AIKITAPI
*/
AIKITAPI int32_t AIKIT_SetMemoryMode(const char* ability,int32_t mode);
typedef int32_t(*AIKIT_SetMemoryModePtr)(const char* ability,int32_t mode);
/**
* 设置日志级别,模式,以及保存路径,旧版日志接口,不推荐使用
* @param level 日志级别
* @param mode 日志输出模式
* @param level 输出模式为文件时的文件名称
* @return 错误码 0=成功,其他表示失败
*/
AIKITAPI int32_t AIKIT_SetLogInfo(int32_t level, int32_t mode, const char* path);
typedef int32_t(*AIKIT_SetLogInfo_Ptr)(int32_t level, int32_t mode, const char* path);
#ifdef __cplusplus
}
#endif
#endif // AIKIT_BIZ_API_C_H

View File

@@ -0,0 +1,299 @@
#ifndef AIKIT_BIZ_BUILDER_H
#define AIKIT_BIZ_BUILDER_H
#include "aikit_biz_type.h"
#include "aikit_biz_obsolete_builder.h"
namespace AIKIT {
/**
* 参数构造类
*/
class AIKITAPI AIKIT_ParamBuilder {
public:
static AIKIT_ParamBuilder* create();
static void destroy(AIKIT_ParamBuilder* builder);
virtual ~AIKIT_ParamBuilder();
//当需要为能力会话设置header字段时需调用此接口
//后续调用param设置的kv对将全部追加到header字段
virtual AIKIT_ParamBuilder* header() = 0;
virtual AIKIT_ParamBuilder* header(const char* key, const char* data, uint32_t dataLen) = 0;
virtual AIKIT_ParamBuilder* header(const char* key, int value) = 0;
virtual AIKIT_ParamBuilder* header(const char* key, double value) = 0;
virtual AIKIT_ParamBuilder* header(const char* key, bool value) = 0;
//当需要设置能力会话功能参数时,需调用此接口
//后续调用param设置的kv对将全部追加到能力功能参数字段
virtual AIKIT_ParamBuilder* service(const char* serviceID) = 0;
virtual AIKIT_ParamBuilder* service(const char* serviceID, AIKIT_ParamBuilder* value) = 0;
virtual AIKIT_ParamBuilder* param(const char* key, const char* data, uint32_t dataLen) = 0;
virtual AIKIT_ParamBuilder* param(const char* key, int value) = 0;
virtual AIKIT_ParamBuilder* param(const char* key, double value) = 0;
virtual AIKIT_ParamBuilder* param(const char* key, bool value) = 0;
//输出控制参数对设置
virtual AIKIT_ParamBuilder* param(const char* key, AIKIT_ParamBuilder* value) = 0;
virtual AIKIT_BizParam* build() = 0;
virtual void clear() = 0;
};
/**
* @brief 数据构造类基类
*
*/
class AIKITAPI AiData {
public:
template <class T, class O>
class AIKITAPI AiDataHolder {
public:
virtual T* status(int status) = 0;
virtual T* begin() = 0;
virtual T* cont() = 0;
virtual T* end() = 0;
virtual T* once() = 0;
virtual T* data(const char* value,int dataLen) = 0;
virtual T* path(const char* path) = 0;
virtual T* file(const FILE* file) = 0;
virtual O* valid() = 0;
};
virtual ~AiData() = 0;
};
/**
* 文本数据构造类
* encoding 文本编码格式设置 默认值:"utf8" 常见取值:"utf8" "gbk" "gb2312"
* compress 文本压缩格式设置 默认值:"raw" 常见取职:"raw" "gzip"
* format 文本内容格式设置 默认值:"plain" 常见取值:"plain" "json" "xml"
*/
class AIKITAPI AiText : public AiData {
public:
class AiTextHolder : public AiDataHolder<AiTextHolder,AiText> {
public:
virtual AiTextHolder* encoding(const char* encoding) = 0;
virtual AiTextHolder* compress(const char* compress) = 0;
virtual AiTextHolder* format(const char* format) = 0;
};
static AiTextHolder* get(const char* key);
virtual ~AiText() = 0;
public:
static constexpr char* const ENCODING_UTF8 = (char*)"utf8";
static constexpr char* const ENCODING_GBK = (char*)"gbk";
static constexpr char* const ENCODING_GB2312 = (char*)"gb2312";
static constexpr char* const ENCODING_DEF = ENCODING_UTF8;
static constexpr char* const COMPRESS_RAW = (char*)"raw";
static constexpr char* const COMPRESS_GZIP = (char*)"gzip";
static constexpr char* const COMPRESS_DEF = COMPRESS_RAW;
static constexpr char* const FORMAT_PLAIN = (char*)"plain";
static constexpr char* const FORMAT_JSON = (char*)"json";
static constexpr char* const FORMAT_XML = (char*)"xml";
static constexpr char* const FORMAT_DEF = FORMAT_PLAIN;
};
/**
* 音频数据构造类
* encoding 音频编码格式设置 默认值:"speex-wb" 常见取值:"lame" "speex" "speex-wb" "opus" "opus-wb" "mp3" "wav" "amr"
* sampleRate 音频采样率设置 默认值:16000 常见取值:16000 8000
* channels 音频声道数设置 默认值:1 常见取值:1 2
* bitDepth 音频数据位深设置 默认值:16 常见取值:[16,8]
*/
class AIKITAPI AiAudio : public AiData {
public :
class AiAudioHolder : public AiDataHolder<AiAudioHolder,AiAudio> {
public:
virtual AiAudioHolder* encoding(const char* encoding) = 0;
virtual AiAudioHolder* sampleRate(int sampleRate) = 0;
virtual AiAudioHolder* channels(int channels) = 0;
virtual AiAudioHolder* bitDepth(int bitDepth) = 0;
};
static AiAudioHolder* get(const char* key);
virtual ~AiAudio() = 0;
public:
static constexpr char* const ENCODING_PCM = (char*)"pcm";
static constexpr char* const ENCODING_RAW = (char*)"raw";
static constexpr char* const ENCODING_ICO = (char*)"ico";
static constexpr char* const ENCODING_SPEEX = (char*)"speex";
static constexpr char* const ENCODING_SPEEX_WB = (char*)"speex-wb";
static constexpr char* const ENCODING_LAME = (char*)"lame";
static constexpr char* const ENCODING_OPUS = (char*)"opus";
static constexpr char* const ENCODING_OPUS_WB = (char*)"opus-wb";
static constexpr char* const ENCODING_WAV = (char*)"wav";
static constexpr char* const ENCODING_AMR = (char*)"amr";
static constexpr char* const ENCODING_AMR_WB = (char*)"amr-wb";
static constexpr char* const ENCODING_MP3 = (char*)"mp3";
static constexpr char* const ENCODING_CDA = (char*)"cda";
static constexpr char* const ENCODING_WAVE = (char*)"wave";
static constexpr char* const ENCODING_AIFF = (char*)"aiff";
static constexpr char* const ENCODING_MPEG = (char*)"mpeg";
static constexpr char* const ENCODING_MID = (char*)"mid";
static constexpr char* const ENCODING_WMA = (char*)"wma";
static constexpr char* const ENCODING_RA = (char*)"ra";
static constexpr char* const ENCODING_RM = (char*)"rm";
static constexpr char* const ENCODING_RMX = (char*)"rmx";
static constexpr char* const ENCODING_VQF = (char*)"vqf";
static constexpr char* const ENCODING_OGG = (char*)"ogg";
static constexpr char* const ENCODING_DEF = ENCODING_SPEEX_WB;
static const int SAMPLE_RATE_8K = 8000;
static const int SAMPLE_RATE_16K = 16000;
static const int SAMPLE_RATE_DEF = SAMPLE_RATE_16K;
static const int CHANNELS_1 = 1;
static const int CHANNELS_2 = 2;
static const int CHANNELS_DEF = CHANNELS_1;
static const int BIT_DEPTH_8 = 8;
static const int BIT_DEPTH_16 = 16;
static const int BIT_DEPTH_DEF = BIT_DEPTH_16;
};
/**
* 图片数据构造类
* encoding 图片编码格式设置 默认值:"jpg" 常见取值:"raw" "rgb" "bgr" "yuv" "jpg" "jpeg" "png" "bmp"
* width 图片宽度设置 默认值:null 常见取值:根据具体图片文件各不相同
* height 图片高度设置 默认值:null 常见取值:根据具体图片文件各不相同
* dims 图片深度设置 默认值:null 常见取值:根据具体图片文件各不相同
*/
class AIKITAPI AiImage : public AiData {
public:
class AiImageHolder : public AiDataHolder<AiImageHolder,AiImage> {
public:
virtual AiImageHolder* encoding(const char* encoding) = 0;
virtual AiImageHolder* width(int width) = 0;
virtual AiImageHolder* height(int height) = 0;
virtual AiImageHolder* dims(int dims) = 0;
};
static AiImageHolder* get(const char* key);
virtual ~AiImage() = 0;
public:
static constexpr char* const ENCODING_RAW = (char*)"raw";
static constexpr char* const ENCODING_JPG = (char*)"jpg";
static constexpr char* const ENCODING_JPEG = (char*)"jpeg";
static constexpr char* const ENCODING_PNG = (char*)"png";
static constexpr char* const ENCODING_APNG = (char*)"apng";
static constexpr char* const ENCODING_BMP = (char*)"bmp";
static constexpr char* const ENCODING_WEBP = (char*)"webp";
static constexpr char* const ENCODING_TIFF = (char*)"tiff";
static constexpr char* const ENCODING_RGB565 = (char*)"rgb565";
static constexpr char* const ENCODING_RGB888 = (char*)"rgb888";
static constexpr char* const ENCODING_BGR565 = (char*)"bgr565";
static constexpr char* const ENCODING_BGR888 = (char*)"bgr888";
static constexpr char* const ENCODING_YUV12 = (char*)"yuv12";
static constexpr char* const ENCODING_YUV21 = (char*)"yuv21";
static constexpr char* const ENCODING_YUV420 = (char*)"yuv420";
static constexpr char* const ENCODING_YUV422 = (char*)"yuv422";
static constexpr char* const ENCODING_PSD = (char*)"psd";
static constexpr char* const ENCODING_PCD = (char*)"pcd";
static constexpr char* const ENCODING_DEF = ENCODING_JPG;
};
/**
* 视频数据构造类
* encoding 视频编码设置 默认值:"h264" 常见取值:"avi" "rmvb" "flv" "h26x" "mpeg"
* width 视频宽度设置 默认值:null 常见取值:根据具体视频文件各不相同
* height 视频高度设置 默认值:null 常见取值:根据具体视频文件各不相同
* frameRate 视频帧率设置 默认值:null 常见取值:根据具体视频文件各不相同
*/
class AIKITAPI AiVideo : public AiData {
public:
class AiVideoHolder : public AiDataHolder<AiVideoHolder,AiVideo> {
public:
virtual AiVideoHolder* encoding(const char* key) = 0;
virtual AiVideoHolder* width(int width) = 0;
virtual AiVideoHolder* height(int height) = 0;
virtual AiVideoHolder* frameRate(int frameRate) = 0;
};
static AiVideoHolder* get(const char* key);
virtual ~AiVideo() = 0;
public:
static constexpr char* const ENCODING_H264 = (char*)"h264";
static constexpr char* const ENCODING_H265 = (char*)"h265";
static constexpr char* const ENCODING_AVI = (char*)"avi";
static constexpr char* const ENCODING_NAVI = (char*)"navi";
static constexpr char* const ENCODING_MP4 = (char*)"mp4";
static constexpr char* const ENCODING_RM = (char*)"rm";
static constexpr char* const ENCODING_RMVB = (char*)"rmvb";
static constexpr char* const ENCODING_MKV = (char*)"mkv";
static constexpr char* const ENCODING_FLV = (char*)"flv";
static constexpr char* const ENCODING_F4V = (char*)"f4v";
static constexpr char* const ENCODING_MPG = (char*)"mpg";
static constexpr char* const ENCODING_MLV = (char*)"mlv";
static constexpr char* const ENCODING_MPE = (char*)"mpe";
static constexpr char* const ENCODING_MPEG = (char*)"mpeg";
static constexpr char* const ENCODING_DAT = (char*)"dat";
static constexpr char* const ENCODING_m2v = (char*)"m2v";
static constexpr char* const ENCODING_VOB = (char*)"vob";
static constexpr char* const ENCODING_ASF = (char*)"asf";
static constexpr char* const ENCODING_MOV = (char*)"mov";
static constexpr char* const ENCODING_WMV = (char*)"wmv";
static constexpr char* const ENCODING_3GP = (char*)"3gp";
static constexpr char* const ENCODING_DEF = ENCODING_H264;
};
/**
* 输入构造类
*/
class AIKITAPI AIKIT_DataBuilder : public AIKIT_DataBuilderObsolete {
public:
static AIKIT_DataBuilder* create();
static void destroy(AIKIT_DataBuilder* builder);
virtual ~AIKIT_DataBuilder();
virtual AIKIT_DataBuilder* payload(AiData* data) = 0;
virtual AIKIT_InputData* build() = 0;
virtual void clear() = 0;
};
/**
* 自定义数据构造器
*/
class AIKITAPI AIKIT_CustomBuilder {
public:
static AIKIT_CustomBuilder* create();
static void destroy(AIKIT_CustomBuilder* builder);
virtual ~AIKIT_CustomBuilder();
virtual AIKIT_CustomBuilder* text(const char* key, const char* data, uint32_t dataLen, int32_t index) = 0;
virtual AIKIT_CustomBuilder* textPath(const char* key, const char* path, int32_t index) = 0;
virtual AIKIT_CustomBuilder* textFile(const char* key, const FILE* file, int32_t index) = 0;
virtual AIKIT_CustomBuilder* audio(const char* key, const char* data, uint32_t dataLen, int32_t index) = 0;
virtual AIKIT_CustomBuilder* audioPath(const char* key, const char* path, int32_t index) = 0;
virtual AIKIT_CustomBuilder* audioFile(const char* key, const FILE* file, int32_t index) = 0;
virtual AIKIT_CustomBuilder* image(const char* key, const char* data, uint32_t dataLen, int32_t index) = 0;
virtual AIKIT_CustomBuilder* imagePath(const char* key, const char* path, int32_t index) = 0;
virtual AIKIT_CustomBuilder* imageFile(const char* key, const FILE* file, int32_t index) = 0;
virtual AIKIT_CustomBuilder* video(const char* key, const char* data, uint32_t dataLen, int32_t index) = 0;
virtual AIKIT_CustomBuilder* videoPath(const char* key, const char* path, int32_t index) = 0;
virtual AIKIT_CustomBuilder* videoFile(const char* key, const FILE* file, int32_t index) = 0;
virtual AIKIT_CustomData* build() = 0;
virtual void clear() = 0;
};
/**
* 参数和输入统一构造类
*/
class AIKITAPI AIKIT_Builder {
public:
static AIKIT_BizParam* build(AIKIT_ParamBuilder* param);
static AIKIT_InputData* build(AIKIT_DataBuilder* data);
static AIKIT_CustomData* build(AIKIT_CustomBuilder* custom);
};
} // namespace AIKIT
#endif

View File

@@ -0,0 +1,158 @@
#ifndef AIKIT_BIZ_CONFIG_H
#define AIKIT_BIZ_CONFIG_H
#include <iostream>
#include "aikit_biz_type.h"
namespace AIKIT {
class ConfigBuilder;
class AppBuilder;
class AuthBuilder;
class LogBuilder;
class CodecBuilder;
class AIKITAPI AIKIT_Configurator {
public:
static ConfigBuilder& builder();
};
class AIKITAPI ConfigBuilder {
public:
/**
* @brief 用户app信息配置
*/
AppBuilder& app();
/**
* @brief 授权相关配置
*/
AuthBuilder& auth();
/**
* @brief 日志相关配置
*/
LogBuilder& log();
/**
* @brief 音视频编解码相关配置
*/
CodecBuilder& codec();
};
class AIKITAPI AppBuilder : public ConfigBuilder {
public:
/**
* @brief 配置应用id
*/
AppBuilder& appID(const char* appID);
/**
* @brief 配置应用key
*/
AppBuilder& apiKey(const char* apiKey);
/**
* @brief 配置应用secret
*/
AppBuilder& apiSecret(const char* apiSecret);
/**
* @brief 配置sdk工作目录需可读可写权限
*/
AppBuilder& workDir(const char* workDir);
/**
* @brief 配置只读资源存放目录, 需可读权限
*/
AppBuilder& resDir(const char* resDir);
/**
* @brief 配置文件路径,包括文件名
*/
AppBuilder& cfgFile(const char* cfgFile);
};
class AIKITAPI AuthBuilder : public ConfigBuilder {
public:
/**
* @brief 配置授权方式 0=设备级授权1=应用级授权
*/
AuthBuilder& authType(int authType);
/**
* @brief 配置离线激活方式的授权文件路径,为空时需联网进行首次在线激活
*/
AuthBuilder& licenseFile(const char* licenseFile);
/**
* @brief 配置授权渠道Id
*/
AuthBuilder& channelID(const char* channelID);
/**
* @brief 配置用户自定义设备标识
*/
AuthBuilder& UDID(const char* UDID);
/**
* @brief 配置授权离线能力,如需配置多个能力,可用";"分隔开, 如"xxx;xxx"
*/
AuthBuilder& ability(const char* ability);
/**
* @brief 配置单个在线能力id及对应请求地址如需配置多个能力可多次调用
* url格式参考https://cn-huadong-1.xf-yun.com/v1/private/xxx:443
*/
AuthBuilder& abilityURL(const char* ability, const char* url);
};
/**
* @brief 配置SDK日志信息
*/
class AIKITAPI LogBuilder : public ConfigBuilder {
public:
/**
* @brief 配置日志等级
*/
LogBuilder& logLevel(int32_t level);
/**
* @brief 配置日志输出模式
*/
LogBuilder& logMode(int32_t mode);
/**
* @brief 配置日志文件保存文件
*/
LogBuilder& logPath(const char* path);
};
/**
* @brief 配置SDK音视频编解码方式
*/
class AIKITAPI CodecBuilder : public ConfigBuilder {
public:
/**
* @brief 配置全局音频编码选项
*/
CodecBuilder& audioEncoding(const char* encodingType);
/**
* @brief 配置能力级音频编码选项
*/
CodecBuilder& audioEncoding(const char* abilityID, const char* encodingType);
/**
* @brief 配置全局音频解码选项
*/
CodecBuilder& audioDecoding(const char* encodingType);
/**
* @brief 配置能力级音频解码选项
*/
CodecBuilder& audioDecoding(const char* abilityID, const char* encodingType);
};
} // end of namespace AIKIT
#endif

View File

@@ -0,0 +1,35 @@
#ifndef AIKIT_BIZ_OBSOLETE_BUILDER_H
#define AIKIT_BIZ_OBSOLETE_BUILDER_H
#include "aikit_biz_type.h"
namespace AIKIT {
class AIKIT_ParamBuilder;
class AIKITAPI AIKIT_DataBuilderObsolete {
public:
//设置SDK输入数据格式描述参数
attribute_deprecated virtual AIKIT_DataBuilderObsolete* desc(const char* key, AIKIT_ParamBuilder* builder) = 0;
attribute_deprecated virtual AIKIT_DataBuilderObsolete* text(const char* key, const char* data, uint32_t dataLen, uint32_t dataStatus) = 0;
attribute_deprecated virtual AIKIT_DataBuilderObsolete* textPath(const char* key, const char* path) = 0;
attribute_deprecated virtual AIKIT_DataBuilderObsolete* textFile(const char* key, const FILE* file) = 0;
attribute_deprecated virtual AIKIT_DataBuilderObsolete* audio(const char* key, const char* data, uint32_t dataLen, uint32_t dataStatus) = 0;
attribute_deprecated virtual AIKIT_DataBuilderObsolete* audioPath(const char* key, const char* path) = 0;
attribute_deprecated virtual AIKIT_DataBuilderObsolete* audioFile(const char* key, const FILE* file) = 0;
attribute_deprecated virtual AIKIT_DataBuilderObsolete* image(const char* key, const char* data, uint32_t dataLen, uint32_t dataStatus) = 0;
attribute_deprecated virtual AIKIT_DataBuilderObsolete* imagePath(const char* key, const char* path) = 0;
attribute_deprecated virtual AIKIT_DataBuilderObsolete* imageFile(const char* key, const FILE* file) = 0;
attribute_deprecated virtual AIKIT_DataBuilderObsolete* video(const char* key, const char* data, uint32_t dataLen, uint32_t dataStatus) = 0;
attribute_deprecated virtual AIKIT_DataBuilderObsolete* videoPath(const char* key, const char* path) = 0;
attribute_deprecated virtual AIKIT_DataBuilderObsolete* videoFile(const char* key, const FILE* file) = 0;
};
} // namespace AIKIT
#endif

View File

@@ -0,0 +1,105 @@
#ifndef __AIKIT_BIZ_TYPE_H__
#define __AIKIT_BIZ_TYPE_H__
#include <stdio.h>
#include "aikit_type.h"
/**
* AIKIT API type
*/
#if defined(_MSC_VER) /* Microsoft Visual C++ */
# if !defined(AIKITAPI)
# if defined(AIKIT_EXPORT)
# define AIKITAPI __declspec(dllexport)
# else
# define AIKITAPI __declspec(dllimport)
# endif
# endif
#elif defined(__BORLANDC__) /* Borland C++ */
# if !defined(AIKITAPI)
# define AIKITAPI __stdcall
# endif
#elif defined(__WATCOMC__) /* Watcom C++ */
# if !defined(AIKITAPI)
# define AIKITAPI __stdcall
# endif
#else /* Any other including Unix */
# if !defined(AIKITAPI)
# if defined(AIKIT_EXPORT)
# define AIKITAPI __attribute__ ((visibility("default")))
# else
# define AIKITAPI
# endif
# endif
#endif
#if defined(_MSC_VER)
# if !defined(attribute_deprecated)
# define attribute_deprecated __declspec(deprecated)
# endif
#else
# if !defined(attribute_deprecated)
# define attribute_deprecated __attribute__((deprecated))
# endif
#endif
#ifndef int32_t
typedef int int32_t;
#endif
#ifndef uint32_t
typedef unsigned int uint32_t;
#endif
typedef AIKIT_BaseParam AIKIT_BizParam;
typedef AIKIT_BaseParamPtr AIKIT_BizParamPtr;
typedef AIKIT_BaseData AIKIT_BizData;
typedef AIKIT_BaseDataPtr AIKIT_BizDataPtr;
typedef AIKIT_BizData AIKIT_InputData;
typedef struct _AIKIT_BaseDataList {
AIKIT_BaseData *node; // 链表节点
int32_t count; // 链表节点个数
int32_t totalLen; // 链表节点所占内存空间count*(sizeof(AIKIT_BaseData)+strlen(node->key)+node->len)
} AIKIT_BaseDataList, *AIKIT_BaseDataListPtr; // 配置对复用该结构定义
typedef struct _AIKIT_BaseParamList {
AIKIT_BaseParam *node; // 链表节点
int32_t count; // 链表节点个数
int32_t totalLen; // 链表节点所占内存空间count*(sizeof(AIKIT_BaseParam)+strlen(node->key)+node->len)
} AIKIT_BaseParamList, *AIKIT_BaseParamListPtr; // 配置对复用该结构定义
typedef AIKIT_BaseDataList AIKIT_OutputData;
typedef AIKIT_BaseParamList AIKIT_OutputEvent;
typedef struct {
void* usrContext;
const char* abilityID;
size_t handleID;
} AIKIT_HANDLE;
typedef void (*AIKIT_OnOutput)(AIKIT_HANDLE* handle, const AIKIT_OutputData* output);
typedef void (*AIKIT_OnEvent)(AIKIT_HANDLE* handle, AIKIT_EVENT eventType, const AIKIT_OutputEvent* eventValue);
typedef void (*AIKIT_OnError)(AIKIT_HANDLE* handle, int32_t err, const char* desc);
typedef struct {
AIKIT_OnOutput outputCB; //輸出回调
AIKIT_OnEvent eventCB; //事件回调
AIKIT_OnError errorCB; //错误回调
} AIKIT_Callbacks;
typedef struct {
int authType; // 授权方式0=设备级授权1=应用级授权
const char* appID; // 应用id
const char* apiKey; // 应用key
const char* apiSecret; // 应用secret
const char* workDir; // sdk工作目录需可读可写权限
const char* resDir; // 只读资源存放目录,需可读权限
const char* licenseFile; // 离线激活方式的授权文件存放路径,为空时需联网进行首次在线激活
const char* batchID; // 授权批次
const char* UDID; // 用户自定义设备标识
const char* cfgFile; // 配置文件路径,包括文件名
} AIKIT_InitParam;
#endif // __AIKIT_BIZ_TYPE_H__

View File

@@ -0,0 +1,12 @@
#ifndef AIKIT_COMMON_H
#define AIKIT_COMMON_H
typedef enum AIKIT_DATA_PTR_TYPE_E {
AIKIT_DATA_PTR_MEM = 0, // 数据内存指针
AIKIT_DATA_PTR_FILE = 1, // 数据文件指针FILE指针
AIKIT_DATA_PTR_PATH = 2 // 数据文件路径指针
} AIKIT_DATA_PTR_TYPE;
#endif //AIKIT_COMMON_H

View File

@@ -0,0 +1,35 @@
/*
* @Description:
* @version:
* @Author: xkzhang9
* @Date: 2020-10-28 10:20:45
* @LastEditors: rfge
* @LastEditTime: 2020-12-28 11:14:20
*/
#ifndef AIKIT_CONSTANT_H
#define AIKIT_CONSTANT_H
typedef enum {
LOG_STDOUT = 0,
LOG_LOGCAT,
LOG_FILE,
} AIKIT_LOG_MODE;
typedef enum {
LOG_LVL_VERBOSE,
LOG_LVL_DEBUG,
LOG_LVL_INFO,
LOG_LVL_WARN,
LOG_LVL_ERROR,
LOG_LVL_CRITICAL,
LOG_LVL_OFF = 100,
} AIKIT_LOG_LEVEL;
typedef enum {
MEMORY_FULL_MODE,
MEMORY_FRIENDLY_MODE
} AIKIT_MEMORY_MODE;
#endif //AIKIT_CONSTANT_H

View File

@@ -0,0 +1,161 @@
/*
* @Description:
* @version:
* @Author: rfge
* @Date: 2020-11-01 17:56:53
* @LastEditors: rfge
* @LastEditTime: 2020-12-14 14:26:44
*/
//
// Created by xkzhang9 on 2020/6/10.
//
#ifndef AIKIT_ERR_H
#define AIKIT_ERR_H
typedef enum {
AIKIT_ERR_AUTH = 18000, // 授权部分
AIKIT_ERR_RES = AIKIT_ERR_AUTH + 100, // 资源部分
AIKIT_ERR_ENG = AIKIT_ERR_AUTH + 200, // 引擎部分
AIKIT_ERR_SDK = AIKIT_ERR_AUTH + 300, // SDK部分
AIKIT_ERR_SYS = AIKIT_ERR_AUTH + 400, // 系统部分
AIKIT_ERR_PARAM = AIKIT_ERR_AUTH + 500, // 参数部分
AIKIT_ERR_PROTOCOL = AIKIT_ERR_AUTH + 600, // 协议部分
AIKIT_ERR_CLOUD = AIKIT_ERR_AUTH + 700, // 云端错误
AIKIT_ERR_LOCAL_NET= AIKIT_ERR_AUTH + 800, //本地网络网络
AIKIT_ERR_VMS = AIKIT_ERR_AUTH + 900, // 虚拟人错误
AIKIT_ERR_SPARK = AIKIT_ERR_AUTH + 950, // spark大模型错误
AIKIT_ERR_OTHER = 0xFF00 // 其他
} AIKIT_ERR_SECTION;
typedef enum {
AIKIT_ERR_SUCCESS = 0, // 操作成功
AIKIT_ERR_GENERAL_FAILED = 1, // 一般错误
AIKIT_ERR_AUTH_LICENSE_NOT_FOUND = AIKIT_ERR_AUTH + 0, // 18000 本地license文件不存在
AIKIT_ERR_AUTH_LICENSE_FILE_INVALID = AIKIT_ERR_AUTH + 1, // 18001 授权文件内容非法
AIKIT_ERR_AUTH_LICENSE_PARSE_FAILED = AIKIT_ERR_AUTH + 2, // 18002 授权文件解析失败
AIKIT_ERR_AUTH_PAYLOAD_DEFECT = AIKIT_ERR_AUTH + 3, // 18003 payload内容缺失
AIKIT_ERR_AUTH_SIGN_DEFECT = AIKIT_ERR_AUTH + 4, // 18004 signature内容缺失
AIKIT_ERR_AUTH_EXPIRED = AIKIT_ERR_AUTH + 5, // 18005 授权已过期
AIKIT_ERR_AUTH_TIME_ERROR = AIKIT_ERR_AUTH + 6, // 18006 授权时间错误比正常时间慢30分钟以上
AIKIT_ERR_AUTH_APP_NOT_MATCH = AIKIT_ERR_AUTH + 7, // 18007 授权应用不匹配apiKey、apiSecret
AIKIT_ERR_AUTH_LICENSE_EXPIRED = AIKIT_ERR_AUTH + 8, // 18008 授权文件激活过期
AIKIT_ERR_AUTH_NULL_APP_PTR = AIKIT_ERR_AUTH + 9, // 18009 授权app信息指针为空
AIKIT_ERR_AUTH_PLATFORM_NOT_MATCH = AIKIT_ERR_AUTH + 10, // 18010 离线授权激活文件指定平台与设备平台不匹配
AIKIT_ERR_AUTH_ARCH_NOT_MATCH = AIKIT_ERR_AUTH + 11, // 18011 离线授权激活文件指定架构与设备cpu架构不匹配
AIKIT_ERR_AUTH_WRONG_LICENSE_NUM = AIKIT_ERR_AUTH + 12, // 18012 离线授权激活文件中包含License个数异常
AIKIT_ERR_AUTH_DEVICE_NOT_FOUND = AIKIT_ERR_AUTH + 13, // 18013 离线授权激活文件中未找到当前设备
AIKIT_ERR_AUTH_LEVEL_NOT_VALID = AIKIT_ERR_AUTH + 14, // 18014 离线授权激活文件中设备指纹安全等级非法
AIKIT_ERR_AUTH_HARDWARE_FAILED = AIKIT_ERR_AUTH + 15, // 18015 硬件授权验证失败
AIKIT_ERR_AUTH_OFFLINE_PROT_INVALID = AIKIT_ERR_AUTH + 16, // 18016 离线授权激活文件内容非法
AIKIT_ERR_AUTH_HEADER_INVALID = AIKIT_ERR_AUTH + 17, // 18017 离线授权激活文件中协议头非法
AIKIT_ERR_AUTH_PART_COUNT_INVALID = AIKIT_ERR_AUTH + 18, // 18018 离线授权激活文件中指纹组成项个数为0
AIKIT_ERR_AUTH_RESOURCE_EXPIREd = AIKIT_ERR_AUTH + 19, // 18019 资源已过期
AIKIT_ERR_RES_VERIFY_FAILED = AIKIT_ERR_RES + 0, // 18100 资源鉴权失败
AIKIT_ERR_RES_INVALID_HEADER= AIKIT_ERR_RES + 1, // 18101 资源格式解析失败
AIKIT_ERR_RES_NOT_MATCH = AIKIT_ERR_RES + 2, // 18102 资源(与引擎)不匹配
AIKIT_ERR_RES_NULL_PTR = AIKIT_ERR_RES + 3, // 18103 资源参数不存在指针为NULL
AIKIT_ERR_RES_OPEN_FAILED = AIKIT_ERR_RES + 4, // 18104 资源路径打开失败
AIKIT_ERR_RES_LOAD_FAILED = AIKIT_ERR_RES + 5, // 18105 资源加载失败workDir内未找到对应资源
AIKIT_ERR_RES_UNLOAD_FAILED = AIKIT_ERR_RES + 6, // 18106 资源卸载失败, 卸载的资源未加载过
AIKIT_ERR_ENG_VERIFY_FAILED = AIKIT_ERR_ENG + 0, // 18200 引擎鉴权失败
AIKIT_ERR_ENG_LOAD_FAILED = AIKIT_ERR_ENG + 1, // 18201 引擎动态加载失败
AIKIT_ERR_ENG_NOT_INITED = AIKIT_ERR_ENG + 2, // 18202 引擎未初始化
AIKIT_ERR_ENG_API_NOT_SUPPORT = AIKIT_ERR_ENG + 3, // 18203 引擎不支持该接口调用
AIKIT_ERR_ENG_NULL_CREATE_PTR = AIKIT_ERR_ENG + 4, // 18204 引擎craete函数指针为空
AIKIT_ERR_SDK_INVALID = AIKIT_ERR_SDK + 0, // 18300 sdk不可用
AIKIT_ERR_SDK_NOT_INITED = AIKIT_ERR_SDK + 1, // 18301 sdk没有初始化
AIKIT_ERR_SDK_INIT_FAILED = AIKIT_ERR_SDK + 2, // 18302 sdk初始化失败
AIKIT_ERR_SDK_ALREADY_INIT = AIKIT_ERR_SDK + 3, // 18303 sdk已经初始化
AIKIT_ERR_SDK_INVALID_PARAM = AIKIT_ERR_SDK + 4, // 18304 sdk不合法参数
AIKIT_ERR_SDK_NULL_SESSION_HANDLE = AIKIT_ERR_SDK + 5, // 18305 sdk会话handle为空
AIKIT_ERR_SDK_SESSION_NOT_FOUND = AIKIT_ERR_SDK + 6, // 18306 sdk会话未找到
AIKIT_ERR_SDK_SESSION_ALREADY_END = AIKIT_ERR_SDK + 7, // 18307 sdk会话重复终止
AIKIT_ERR_SDK_TIMEOUT = AIKIT_ERR_SDK + 8, // 18308 超时错误
AIKIT_ERR_SDK_INITING = AIKIT_ERR_SDK + 9, // 18309 sdk正在初始化中
AIKIT_ERR_SDK_SESSEION_ALREAY_START = AIKIT_ERR_SDK + 10, // 18310 sdk会话重复开启
AIKIT_ERR_SDK_CONCURRENT_OVERFLOW = AIKIT_ERR_SDK + 11, // 18311 sdk同一能力并发路数超出最大限制
AIKIT_ERR_SYS_WORK_DIR_ILLEGAL = AIKIT_ERR_SYS + 0, // 18400 工作目录无写权限
AIKIT_ERR_SYS_DEVICE_UNKNOWN = AIKIT_ERR_SYS + 1, // 18401 设备指纹获取失败,设备未知
AIKIT_ERR_SYS_FILE_OPEN_FAILED = AIKIT_ERR_SYS + 2, // 18402 文件打开失败
AIKIT_ERR_SYS_MEM_ALLOC_FAILED = AIKIT_ERR_SYS + 3, // 18403 内存分配失败
AIKIT_ERR_SYS_DEVICE_COMPARE_FAILED = AIKIT_ERR_SYS + 4, // 18404 设备指纹比较失败
AIKIT_ERR_SYS_WORK_DIR_NOT_EXIST = AIKIT_ERR_SYS + 5, // 18405 工作目录不存在
AIKIT_ERR_PARAM_NOT_FOUND = AIKIT_ERR_PARAM + 0, // 18500 未找到该参数key
AIKIT_ERR_PARAM_OVERFLOW = AIKIT_ERR_PARAM + 1, // 18501 参数范围溢出,不满足约束条件
AIKIT_ERR_PARAM_NULL_INIT_PARAM_PTR = AIKIT_ERR_PARAM + 2, // 18502 sdk初始化参数为空
AIKIT_ERR_PARAM_NULL_APPID_PTR = AIKIT_ERR_PARAM + 3, // 18503 sdk初始化参数中appid为空
AIKIT_ERR_PARAM_NULL_APIKEY_PTR = AIKIT_ERR_PARAM + 4, // 18504 sdk初始化参数中apiKey为空
AIKIT_ERR_PARAM_NULL_APISECRET_PTR = AIKIT_ERR_PARAM + 5, // 18505 sdk初始化参数中apiSecret为空
AIKIT_ERR_PARAM_NULL_ABILITY_PTR = AIKIT_ERR_PARAM + 6, // 18506 ability参数为空
AIKIT_ERR_PARAM_NULL_INPUT_PTR = AIKIT_ERR_PARAM + 7, // 18507 input参数为空
AIKIT_ERR_PARAM_DATA_KEY_NOT_EXIST = AIKIT_ERR_PARAM + 8, // 18508 输入数据参数Key不存在
AIKIT_ERR_PARAM_REQUIRED_MISSED = AIKIT_ERR_PARAM + 9, // 18509 必填参数缺失
AIKIT_ERR_PARAM_NULL_OUTPUT_PTR = AIKIT_ERR_PARAM + 10, // 18510 output参数缺失
AIKIT_ERR_CODEC_NOT_SUPPORT = AIKIT_ERR_PARAM + 20, // 18520 不支持的编解码类型
AIKIT_ERR_CODEC_NULL_PTR = AIKIT_ERR_PARAM + 21, // 18521 编解码handle指针为空
AIKIT_ERR_CODEC_MODULE_MISSED = AIKIT_ERR_PARAM + 22, // 18522 编解码模块条件编译未打开
AIKIT_ERR_CODEC_ENCODE_FAIL = AIKIT_ERR_PARAM + 23, // 18523 编码错误
AIKIT_ERR_CODEC_DECODE_FAIL = AIKIT_ERR_PARAM + 24, // 18524 解码错误
AIKIT_ERR_VAD_RESPONSE_TIMEOUT = AIKIT_ERR_PARAM + 30, // VAD静音超时
AIKIT_ERR_PROTOCOL_TIMESTAMP_MISSING = AIKIT_ERR_PROTOCOL + 0, // 18600 协议中时间戳字段缺失
AIKIT_ERR_PROTOCOL_ABILITY_NOT_FOUND = AIKIT_ERR_PROTOCOL + 1, // 18601 协议中未找到该能力ID
AIKIT_ERR_PROTOCOL_RESOURCE_NOT_FOUND = AIKIT_ERR_PROTOCOL + 2, // 18602 协议中未找到该资源ID
AIKIT_ERR_PROTOCOL_ENGINE_NOT_FOUND = AIKIT_ERR_PROTOCOL + 3, // 18603 协议中未找到该引擎ID
AIKIT_ERR_PROTOCOL_ZERO_ENGINE_NUM = AIKIT_ERR_PROTOCOL + 4, // 18604 协议中引擎个数为0
AIKIT_ERR_PROTOCOL_NOT_LOADED = AIKIT_ERR_PROTOCOL + 5, // 18605 协议未被初始化解析
AIKIT_ERR_PROTOCOL_INTERFACE_TYPE_NOT_MATCH = AIKIT_ERR_PROTOCOL + 6, // 18606 协议能力接口类型不匹配
AIKIT_ERR_PROTOCOL_TEMP_VERIFY_FAILED = AIKIT_ERR_PROTOCOL + 7, // 18607 预置协议解析失败
AIKIT_ERR_CLOUD_GENERAL_FAILED = AIKIT_ERR_CLOUD + 0, // 18700 通用网络错误
AIKIT_ERR_CLOUD_CONNECT_FAILED = AIKIT_ERR_CLOUD + 1, // 18701 网路不通
AIKIT_ERR_CLOUD_403 = AIKIT_ERR_CLOUD + 2, // 18702 网关检查不过
AIKIT_ERR_CLOUD_WRONG_RSP_FORMAT = AIKIT_ERR_CLOUD + 3, // 18703 云端响应格式不对
AIKIT_ERR_CLOUD_APP_NOT_FOUND = AIKIT_ERR_CLOUD + 4, // 18704 应用未注册
AIKIT_ERR_CLOUD_APP_CHECK_FAILED = AIKIT_ERR_CLOUD + 5, // 18705 应用apiKey&&apiSecret校验失败
AIKIT_ERR_CLOUD_WRONG_ARCHITECT = AIKIT_ERR_CLOUD + 6, // 18706 引擎不支持的平台架构
AIKIT_ERR_CLOUD_AUTH_EXPIRED = AIKIT_ERR_CLOUD + 7, // 18707 授权已过期
AIKIT_ERR_CLOUD_AUTH_FULL = AIKIT_ERR_CLOUD + 8, // 18708 授权数量已满
AIKIT_ERR_CLOUD_ABILITY_NOT_FOUND = AIKIT_ERR_CLOUD + 9, // 18709 未找到该app绑定的能力
AIKIT_ERR_CLOUD_RESOURCE_NOT_FOUND = AIKIT_ERR_CLOUD + 10, // 18710 未找到该app绑定的能力资源
AIKIT_ERR_CLOUD_JSON_PARSE_FAILED = AIKIT_ERR_CLOUD + 11, // 18711 JSON操作失败
AIKIT_ERR_CLOUD_404 = AIKIT_ERR_CLOUD + 12, // 18712 http 404错误
AIKIT_ERR_CLOUD_LEVEL_NOT_MATCH = AIKIT_ERR_CLOUD + 13, // 18713 设备指纹安全等级不匹配
AIKIT_ERR_CLOUD_401 = AIKIT_ERR_CLOUD + 14, // 18714 用户没有访问权限,需要进行身份认证
AIKIT_ERR_CLOUD_SDK_NOT_FOUND = AIKIT_ERR_CLOUD + 15, // 18715 未找到该SDK ID
AIKIT_ERR_CLOUD_ABILITYS_NOT_FOUND = AIKIT_ERR_CLOUD + 16, // 18716 未找到该组合能力集合
AIKIT_ERR_CLOUD_ABILITY_NOT_ENOUGH = AIKIT_ERR_CLOUD + 17, // 18717 SDK组合能力授权不足
AIKIT_ERR_CLOUD_APP_SIG_INVALID = AIKIT_ERR_CLOUD + 18, // 18718 无效授权应用签名
AIKIT_ERR_CLOUD_APP_SIG_NOT_UNIQUE = AIKIT_ERR_CLOUD + 19, // 18719 应用签名不唯一
AIKIT_ERR_CLOUD_SCHEMA_INVALID = AIKIT_ERR_CLOUD + 20, // 18720 能力schema不可用
AIKIT_ERR_CLOUD_TEMPLATE_NOT_FOUND = AIKIT_ERR_CLOUD + 21, // 18721 竞争授权: 未找到能力集模板
AIKIT_ERR_CLOUD_ABILITY_NOT_IN_TEMPLATE = AIKIT_ERR_CLOUD + 22, // 18722 竞争授权: 能力不在模板能力集模板中
AIKIT_ERR_LOCAL_NET_CONNECT_FAILED = AIKIT_ERR_LOCAL_NET + 1, // 18801 连接建立出错
AIKIT_ERR_LOCAL_NET_RES_WAIT_TIMEOUT = AIKIT_ERR_LOCAL_NET + 2, // 18802 结果等待超时
AIKIT_ERR_LOCAL_NET_CONNECT_ERROR = AIKIT_ERR_LOCAL_NET + 3, // 18803 连接状态异常
AIKIT_ERR_VMS_START_ERROR = AIKIT_ERR_VMS + 1, // 18901虚拟人启动错误
AIKIT_ERR_VMS_STOP_ERROR = AIKIT_ERR_VMS + 2, // 18902虚拟人结束错误
AIKIT_ERR_VMS_DRIVE_ERROR = AIKIT_ERR_VMS + 3, // 18903虚拟人驱动错误
AIKIT_ERR_VMS_HEARTBEAT_ERROR = AIKIT_ERR_VMS + 4, // 18904虚拟人心跳报错
AIKIT_ERR_VMS_TIMEOUT = AIKIT_ERR_VMS + 5, // 18905虚拟人服务超时
AIKIT_ERR_VMS_OTHER_ERROR = AIKIT_ERR_VMS + 6, // 18906虚拟人通用报错
AIKIT_ERR_SPARK_REQUEST_UNORDERED = AIKIT_ERR_SPARK + 1, // 18951 同一流式大模型会话,禁止并发交互请求
AIKIT_ERR_SPARK_TEXT_INVALID = AIKIT_ERR_SPARK + 2 // 18952 输入文本格式或内容非法
} AIKIT_ERR;
typedef enum { AIKIT_ERR_TYPE_AUTH = 0, AIKIT_ERR_TYPE_HTTP = 1 } AIKIT_ERR_TYPE;
#endif //AIKIT_ERR_H

View File

@@ -0,0 +1,90 @@
#ifndef AIKIT_SPARK_H
#define AIKIT_SPARK_H
#include "aikit_biz_api.h"
namespace AIKIT {
class AIKITAPI ChatParam {
public:
static ChatParam* builder();
virtual ~ChatParam();
/**
* @brief 配置授权的用户id用于关联用户交互的上下文
*/
virtual ChatParam* uid(const char* uid) = 0;
/**
* @brief 配置chat领域信息
*/
virtual ChatParam* domain(const char* domain) = 0;
/**
* @brief 配置内容审核策略
*/
virtual ChatParam* auditing(const char* auditing) = 0;
/**
* @brief 配置关联会话chat id标识需要保障⽤户下唯⼀
*/
virtual ChatParam* chatID(const char* chatID) = 0;
/**
* @brief 配置核采样阈值默认值0.5,向上调整可以增加结果的随机程度
*/
virtual ChatParam* temperature(const float& temperature) = 0;
/**
* @brief 配置从k个候选中随机选择⼀个⾮等概率默认值4取值范围1-99
*/
virtual ChatParam* topK(const int& topK) = 0;
/**
* @brief 配置回答的tokens的最大长度默认值2048
*/
virtual ChatParam* maxToken(const int& maxToken) = 0;
/**
* @brief 配置chat服务域名地址
*/
virtual ChatParam* url(const char* url) = 0;
/**
* @brief 配置chat扩展功能参数
*/
virtual ChatParam* param(const char* key, const char* value) = 0;
virtual ChatParam* param(const char* key, int value) = 0;
virtual ChatParam* param(const char* key, double value) = 0;
virtual ChatParam* param(const char* key, bool value) = 0;
};
using AIChat_Handle = AIKIT_HANDLE;
typedef void (*onChatOutput)(AIChat_Handle* handle, const char* role, const char* content, const int& index);
typedef void (*onChatToken)(AIChat_Handle* handle, const int& completionTokens, const int& promptTokens, const int& totalTokens);
typedef void (*onChatError)(AIChat_Handle* handle, const int& err, const char* errDesc);
typedef struct {
onChatOutput outputCB; //输出回调
onChatToken tokenCB; //token计算信息回调
onChatError errorCB; //错误回调
} AIKIT_ChatCBS;
AIKITAPI int32_t AIKIT_ChatCallback(const AIKIT_ChatCBS& cbs);
//异步chat
AIKITAPI int32_t AIKIT_AsyncChat(const ChatParam* params, const char* inputText, void* usrContext);
AIKITAPI int32_t AIKIT_Start(const ChatParam* params, void* usrContext, AIChat_Handle** outHandle);
AIKITAPI int32_t AIKIT_Write(AIChat_Handle* handle, const char* inputText);
//
//AIKITAPI int32_t AIKIT_End(AIChat_Handle* handle);
} // end of namespace AIKIT
#endif

View File

@@ -0,0 +1,107 @@
#ifndef __AIKIT_TYPE_H__
#define __AIKIT_TYPE_H__
#include <stddef.h>
#include <stdint.h>
#include "aikit_common.h"
#if defined(_MSC_VER) /* Microsoft Visual C++ */
#pragma pack(push, 8)
#elif defined(__BORLANDC__) /* Borland C++ */
#pragma option -a8
#elif defined(__WATCOMC__) /* Watcom C++ */
#pragma pack(push, 8)
#else /* Any other including Unix */
#endif
typedef enum _AIKIT_VarType {
AIKIT_VarTypeString = 0, // 字符串型
AIKIT_VarTypeInt = 1, // 整型
AIKIT_VarTypeDouble = 2, // 实型
AIKIT_VarTypeBool = 3, // 布尔类型
AIKIT_VarTypeParamPtr = 4, // 子参数类型
AIKIT_VarTypeUnknown = -1 //
} AIKIT_VarType;
typedef enum _AIKIT_DataStatus {
AIKIT_DataBegin = 0, // 首数据
AIKIT_DataContinue = 1, // 中间数据
AIKIT_DataEnd = 2, // 尾数据
AIKIT_DataOnce = 3, // 非会话单次输入输出
} AIKIT_DataStatus;
typedef enum _AIKIT_DataType {
AIKIT_DataText = 0, // 文本数据
AIKIT_DataAudio = 1, // 音频数据
AIKIT_DataImage = 2, // 图像数据
AIKIT_DataVideo = 3, // 视频数据
// AIKIT_DataPer = 4, // 个性化数据
} AIKIT_DataType;
typedef enum {
AIKIT_Event_UnKnown = 0,
AIKIT_Event_Start = 1, // 引擎计算开始事件
AIKIT_Event_End = 2, // 引擎计算结束事件
AIKIT_Event_Timeout = 3, // 引擎计算超时事件
AIKIT_Event_Progress = 4, // 引擎计算进度事件
// 在线能力连接状态事件
AIKIT_Event_Null = 10, // 空连接状态
AIKIT_Event_Init, // 初始状态
AIKIT_Event_Connecting, // 正在连接
AIKIT_Event_ConnTimeout, // 连接超时
AIKIT_Event_Failed, // 连接失败
AIKIT_Event_Connected, // 已经连接
AIKIT_Event_Error, // 收发出错,意味着断开连接
AIKIT_Event_Disconnected, // 断开连接,一般指心跳超时,连接无错误
AIKIT_Event_Closing, // 正在关闭连接
AIKIT_Event_Closed, // 已经关闭连接
AIKIT_Event_Responding, // 网络连接正在响应
AIKIT_Event_ResponseTimeout, // 网络连接响应超时
// VAD事件
AIKIT_Event_VadBegin = 30, // VAD开始
AIKIT_Event_VadEnd // VAD结束
} AIKIT_EVENT;
typedef struct _AIKIT_BaseParam {
struct _AIKIT_BaseParam *next; // 链表指针
const char *key; // 数据标识
void *value; // 数据实体
void* reserved; // 预留字段
int32_t len; // 数据长度
int32_t type; // 变量类型取值参见AIKIT_VarType
} AIKIT_BaseParam, *AIKIT_BaseParamPtr; // 配置对复用该结构定义
typedef struct _AIKIT_BaseData {
struct _AIKIT_BaseData *next; // 链表指针
AIKIT_BaseParam *desc; // 数据描述,包含每个数据(audio/video/text/image)的所有特征参数数据(sample_rate,channels,data等)
const char *key; // 数据标识
void *value; // 数据实体
void* reserved; // 预留字段
int32_t len; // 数据长度
int32_t type; // 数据类型取值参见AIKIT_DataType
int32_t status; // 数据状态取值参见AIKIT_DataStatus
int32_t from; // 数据来源取值参见AIKIT_DATA_PTR_TYPE
} AIKIT_BaseData, *AIKIT_BaseDataPtr;
typedef struct _AIKIT_CustomData {
struct _AIKIT_CustomData* next; // 链表指针
const char* key; // 数据标识
void* value; // 数据内容
void* reserved; // 预留字段
int32_t index; // 数据索引,用户可自定义设置
int32_t len; // 数据长度type非DATA_PTR_FILE时本字段有效
int32_t from; // 数据内容的类型取值参见枚举AIKIT_DATA_PTR_TYPE
} AIKIT_CustomData, *AIKIT_CustomDataPtr;
/* Reset the structure packing alignments for different compilers. */
#if defined(_MSC_VER) /* Microsoft Visual C++ */
#pragma pack(pop)
#elif defined(__BORLANDC__) /* Borland C++ */
#pragma option -a.
#elif defined(__WATCOMC__) /* Watcom C++ */
#pragma pack(pop)
#endif
#endif

View File

@@ -0,0 +1,16 @@
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='aikit_tts',
executable='aikit_tts_node',
name='aikit_tts_node',
parameters=[
{'app_id': '268545c7'},
{'api_key': '9e90faba614489a90252f823127f8348'},
{'api_secret': 'M2U5YjJhYjJlMDYzY2Y2ZDhjNjNiZDJl'}
]
)
])

Binary file not shown.

BIN
aikit_tts/libs/libaikit.so Normal file

Binary file not shown.

0
aikit_tts/output.pcm Normal file
View File

20
aikit_tts/package.xml Normal file
View File

@@ -0,0 +1,20 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>aikit_tts</name>
<version>0.0.0</version>
<description>ROS 2 package for AIKIT TTS</description>
<maintainer email="your@email.com">Your Name</maintainer>
<license>Apache-2.0</license>
<!-- 编译工具依赖 -->
<buildtool_depend>ament_cmake</buildtool_depend>
<!-- 核心依赖depend 同时包含编译和运行时依赖) -->
<depend>rclcpp</depend>
<depend>std_msgs</depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>

View File

@@ -0,0 +1,170 @@
#include <fstream>
#include <assert.h>
#include <cstring>
#include <atomic>
#include <unistd.h>
#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/string.hpp>
#include "aikit_biz_api.h"
#include "aikit_constant.h"
#include "aikit_biz_config.h"
using namespace std;
using namespace AIKIT;
// 全局变量(可改为类成员变量更规范)
FILE *fin = nullptr;
static const char *ABILITY = "e2e44feff";
static std::atomic_bool ttsFinished(false);
rclcpp::Node::SharedPtr g_node = nullptr;
// 回调函数保持不变(输出、事件、错误处理)
void OnOutput(AIKIT_HANDLE* handle, const AIKIT_OutputData* output){
RCLCPP_INFO(g_node->get_logger(), "OnOutput abilityID: %s", handle->abilityID);
RCLCPP_INFO(g_node->get_logger(), "OnOutput key: %s", output->node->key);
if((output->node->value) && (fin != nullptr))
{
fwrite(output->node->value, sizeof(char), output->node->len, fin);
}
}
void OnEvent(AIKIT_HANDLE* handle, AIKIT_EVENT eventType, const AIKIT_OutputEvent* eventValue){
RCLCPP_INFO(g_node->get_logger(), "OnEvent: %d", eventType);
if(eventType == AIKIT_Event_End){
ttsFinished = true;
}
}
void OnError(AIKIT_HANDLE* handle, int32_t err, const char* desc){
RCLCPP_ERROR(g_node->get_logger(), "OnError: %d, desc: %s", err, desc);
}
// 语音合成函数(改为接收字符串参数)
void text_to_speech(const string& text) {
AIKIT_ParamBuilder* paramBuilder = nullptr;
AIKIT_DataBuilder* dataBuilder = nullptr;
AIKIT_HANDLE* handle = nullptr;
AiText* aiText_raw = nullptr;
int ret = 0;
ttsFinished = false;
// -------------------------- 关键调整把tts_params移到goto之前 --------------------------
// 定义TTS参数JSON移到前面避免goto跳过初始化
const char* tts_params = R"({
"vcn": "xiaoyan",
"language": "zh-CN",
"textEncoding": "UTF-8",
"speed": 50,
"pitch": 50,
"volume": 50
})";
// ---------------------------------------------------------------------------------------
// 打开输出文件路径可改为ROS参数配置
fin = fopen("output.pcm", "w+");
if (fin == nullptr){
RCLCPP_ERROR(g_node->get_logger(), "Failed to open output.pcm");
goto exit; // 此时goto不会跨越任何变量初始化
}
// 配置TTS参数
paramBuilder = AIKIT_ParamBuilder::create();
// 将JSON参数作为顶层"params"字段传入SDK能直接解析
paramBuilder->param("params", tts_params, strlen(tts_params));
// 启动AI服务
ret = AIKIT_Start(ABILITY, AIKIT_Builder::build(paramBuilder), nullptr, &handle);
RCLCPP_INFO(g_node->get_logger(), "AIKIT_Start ret: %d", ret);
if(ret != 0) goto exit;
// 发送文本数据
dataBuilder = AIKIT_DataBuilder::create();
aiText_raw = AiText::get("text")->data(text.c_str(), text.length())->valid();
dataBuilder->payload(aiText_raw);
ret = AIKIT_Write(handle, AIKIT_Builder::build(dataBuilder));
RCLCPP_INFO(g_node->get_logger(), "AIKIT_Write ret: %d", ret);
if(ret != 0) goto exit;
// 等待合成完成
while(ttsFinished != true && rclcpp::ok()){
usleep(1000);
}
AIKIT_End(handle); // 释放会话资源
exit:
if(paramBuilder) { delete paramBuilder; paramBuilder = nullptr; }
if(dataBuilder) { delete dataBuilder; dataBuilder = nullptr; }
if(fin) { fclose(fin); fin = nullptr; }
}
// 订阅文本消息的回调函数
void text_callback(const std_msgs::msg::String::SharedPtr msg) {
RCLCPP_INFO(g_node->get_logger(), "Received text: %s", msg->data.c_str());
text_to_speech(msg->data); // 调用TTS合成
}
// ROS 2节点初始化集成AIKIT初始化
class AikitTtsNode : public rclcpp::Node {
public:
AikitTtsNode() : Node("aikit_tts_node") {
// 从参数服务器获取科大讯飞密钥建议在launch文件中配置
this->declare_parameter("app_id", "268545c7");
this->declare_parameter("api_key", "9e90faba614489a90252f823127f8348");
this->declare_parameter("api_secret", "M2U5YjJhYjJlMDYzY2Y2ZDhjNjNiZDJl");
std::string app_id, api_key, api_secret;
app_id = "268545c7";
api_key = "9e90faba614489a90252f823127f8348";
api_secret = "M2U5YjJhYjJlMDYzY2Y2ZDhjNjNiZDJl";
// 初始化AIKIT
AIKIT_Configurator::builder()
.app()
.appID(app_id.c_str())
.apiKey(api_key.c_str())
.apiSecret(api_secret.c_str())
.workDir("./") // 工作目录如果资源还找不到可改为TTS_RESOURCE_PATH
.auth()
.authType(0)
.log()
.logLevel(LOG_LVL_INFO)
.logPath("./");
int ret = AIKIT_Init();
if(ret != 0) {
RCLCPP_FATAL(this->get_logger(), "AIKIT_Init failed: %d", ret);
rclcpp::shutdown();
}
// 注册回调并创建订阅者
AIKIT_Callbacks cbs = {OnOutput, OnEvent, OnError};
AIKIT_RegisterAbilityCallback(ABILITY, cbs);
subscriber_ = this->create_subscription<std_msgs::msg::String>(
"tts_text", 10, text_callback);
RCLCPP_INFO(this->get_logger(), "Aikit TTS node initialized");
}
~AikitTtsNode() {
AIKIT_UnInit(); // 释放AIKIT资源
}
private:
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscriber_;
};
int main(int argc, char *argv[]) {
rclcpp::init(argc, argv);
auto node = std::make_shared<AikitTtsNode>();
g_node = node; // 供回调函数使用
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}

View File

@@ -11,11 +11,12 @@ find_package(sensor_msgs REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rosidl_default_generators REQUIRED)
find_package(message_filters REQUIRED)
find_package(interfaces REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/DistMsg.msg"
DEPENDENCIES sensor_msgs
)
#rosidl_generate_interfaces(${PROJECT_NAME}
# "msg/DistMsg.msg"
# DEPENDENCIES sensor_msgs
#)
include_directories(
include
${CMAKE_CURRENT_BINARY_DIR}
@@ -23,18 +24,16 @@ include_directories(
)
add_executable(dist_dev_node src/main.cpp src/dist_dev_node.cpp src/ks104.cpp)
add_dependencies(dist_dev_node ${PROJECT_NAME}__rosidl_typesupport_cpp)
#add_dependencies(dist_dev_node ${PROJECT_NAME}__rosidl_typesupport_cpp)
target_link_libraries(dist_dev_node
${PROJECT_NAME}__rosidl_typesupport_cpp
${PROJECT_NAME}__rosidl_typesupport_c
message_filters::message_filters
)
ament_target_dependencies(dist_dev_node rclcpp sensor_msgs)
ament_target_dependencies(dist_dev_node rclcpp sensor_msgs interfaces)
install(TARGETS dist_dev_node DESTINATION lib/${PROJECT_NAME})
install(DIRECTORY msg/ DESTINATION share/${PROJECT_NAME})
#install(DIRECTORY msg/ DESTINATION share/${PROJECT_NAME})
install(DIRECTORY launch/ DESTINATION share/${PROJECT_NAME}/launch})
ament_export_dependencies(rosidl_default_runtime)

View File

@@ -5,7 +5,7 @@
#include "message_filters/subscriber.h"
#include "message_filters/time_synchronizer.h"
#include "message_filters/sync_policies/approximate_time.hpp"
#include "dist_dev/msg/dist_msg.hpp"
#include "interfaces/msg/dist_msg.hpp"
using namespace std;
namespace dist_dev{
@@ -16,8 +16,8 @@ namespace dist_dev{
void pub_msg(const sensor_msgs::msg::PointCloud2& pc,const DistCfg& cfg);
void pub_msg(const DistCfg& cfg);
private:
rclcpp::Publisher<dist_dev::msg::DistMsg>::SharedPtr dist_pub;
std::shared_ptr<dist_dev::msg::DistMsg> dist_msg;
rclcpp::Publisher<interfaces::msg::DistMsg>::SharedPtr dist_pub;
std::shared_ptr<interfaces::msg::DistMsg> dist_msg;
};
}

View File

@@ -6,10 +6,13 @@ from launch.conditions import IfCondition
def generate_launch_description():
return LaunchDescription([
ExecuteProcess(
cmd=["ros2","launch","livox_ros_driver2","msg_MID360_launch.py"],
output="screen"
),
# 等待一段时间确保相机启动完成
ExecuteProcess(
cmd=['sleep', '3'],
cmd=['sleep', '2'],
output='screen'
),
Node(

View File

@@ -1,5 +0,0 @@
string source
string type
string position
int32[] ranges
sensor_msgs/PointCloud2 points

View File

@@ -1,15 +1,15 @@
#include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/point_cloud2.hpp>
#include "dist_dev/msg/dist_msg.hpp"// 自定义消息头文件
#include "interfaces/msg/dist_msg.hpp"// 自定义消息头文件
#include "dist_dev/dist_cfg.hpp"
#include <message_filters/subscriber.h>
#include <message_filters/sync_policies/approximate_time.hpp>
#include <message_filters/synchronizer.h>
#include"dist_dev/dist_dev_node.hpp"
namespace dist_dev{
DistSub::DistSub() : Node("DistSub") {
dist_msg= std::make_shared<dist_dev::msg::DistMsg>();
dist_pub = this->create_publisher<dist_dev::msg::DistMsg>("/dist_msg", 10);
DistSub::DistSub() : Node("dist_dev_node") {
dist_msg= std::make_shared<interfaces::msg::DistMsg>();
dist_pub = this->create_publisher<interfaces::msg::DistMsg>("/dist_msg", 10);
}
static long long pub_cnt=0;
void DistSub::pub_msg(const sensor_msgs::msg::PointCloud2& pc,const DistCfg& cfg){
@@ -29,7 +29,7 @@ void DistSub::pub_msg(const DistCfg& cfg){
dist_msg->points= sensor_msgs::msg::PointCloud2();
dist_pub->publish(*dist_msg);
pub_cnt+=1;
std::cout<<"pub msg["<<pub_cnt<<"]:"<<std::endl;
std::cout<<"pub msg["<<pub_cnt<<"]:"<<dist_arr[0]<<","<<dist_arr[1]<<std::endl;
}
}

View File

@@ -7,9 +7,10 @@ static int uart_send(const char *port, int baud, const u8 *tx_data, int tx_len,u
// 1. 打开串口(非阻塞模式)
int fd = open(port, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd < 0) {
perror("open port failed");
printf("open com:%s error\n",port);
return -1;
}
/// printf("now config:%s\n",port);
// 2. 配置串口参数8N1
struct termios options;
tcgetattr(fd, &options);
@@ -54,7 +55,7 @@ static int uart_send(const char *port, int baud, const u8 *tx_data, int tx_len,u
return total_read;
}
static int ks104_send(const u8 *tx,u8* rx){
int ret=uart_send("/dev/ttyUSB0",115200,tx,3,rx,128,100);
int ret=uart_send("/dev/ttyS0",115200,tx,3,rx,128,100);
return ret;
}
int ks104_dist(u8 addr){

View File

@@ -1,5 +1,5 @@
#include <rclcpp/rclcpp.hpp>
#include "dist_dev/msg/dist_msg.hpp"
#include "interfaces/msg/dist_msg.hpp"
#include "dist_dev/dist_cfg.hpp"
#include <message_filters/subscriber.h>
#include <message_filters/sync_policies/approximate_time.hpp>
@@ -22,7 +22,7 @@ int main(int argc, char* argv[]) {
return -1;
}
//超声测距
cur_node->dist_arr.resize(4);
cur_node->dist_arr.resize(2);
rclcpp::TimerBase::SharedPtr timer_=cur_node->create_wall_timer(std::chrono::milliseconds(100), [=]() {
//ls.header.frame_id="ultralsonic";
@@ -30,8 +30,8 @@ int main(int argc, char* argv[]) {
#if 1
cur_node->dist_arr[0]=ks104_dist(0xd0);
cur_node->dist_arr[1]=ks104_dist(0xd2);
cur_node->dist_arr[2]=ks104_dist(0xd4);
cur_node->dist_arr[3]=ks104_dist(0xd6);
// cur_node->dist_arr[2]=ks104_dist(0xd4);
// cur_node->dist_arr[3]=ks104_dist(0xd6);
cur_node->pub_msg(cfg0);
#endif
});

34
drv_test/CMakeLists.txt Normal file
View File

@@ -0,0 +1,34 @@
cmake_minimum_required(VERSION 3.8)
project(drv_test)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(motor_dev REQUIRED)
find_package(interfaces REQUIRED)
find_package(std_msgs REQUIRED)
add_executable(main src/main.cpp)
add_executable(xmain src/xmain.cpp)
ament_target_dependencies(main rclcpp motor_dev interfaces sensor_msgs)
ament_target_dependencies(xmain rclcpp motor_dev interfaces sensor_msgs)
install(TARGETS main DESTINATION lib/${PROJECT_NAME})
install(TARGETS xmain DESTINATION lib/${PROJECT_NAME})
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# comment the line when this package is in a git repo and when
# a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()

23
drv_test/package.xml Normal file
View File

@@ -0,0 +1,23 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>drv_test</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="NuoDaJia02@hivecore.cn">demo</maintainer>
<license>TODO: License declaration</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>std_msg</depend>
<depend>rclcpp</depend>
<depend>sensor_msgs</depend>
<depend>motor_dev</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>

161
drv_test/src/main.cpp Normal file
View File

@@ -0,0 +1,161 @@
#include <chrono>
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "interfaces/msg/motor_cmd.hpp"
#include "interfaces/srv/motor_info.hpp"
#include "interfaces/srv/motor_param.hpp"
#include "std_msgs/msg/string.hpp"
#include "sensor_msgs/msg/image.hpp"
using namespace std::chrono_literals;
static auto g_program_start_time = std::chrono::steady_clock::now();
double log_printf(const char* format, ...) {
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::duration<double>>(now - g_program_start_time);
double seconds = elapsed.count();
double minute=seconds/60;
char buffer[1024];
va_list args;
va_start(args, format);
int n = vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
if (n < 0) {
std::fprintf(stderr, "[%.3f] (log error)\n", seconds);
return 0;
}
std::fprintf(stdout, "[%.3f/%.1f] %s", seconds,minute, buffer);
std::fflush(stdout);
return seconds;
}
using MotorCmd = interfaces::msg::MotorCmd;
using MotorInfo=interfaces::srv::MotorInfo;
using MotorParam=interfaces::srv::MotorParam;
class MotorCmdPublisher : public rclcpp::Node
{
public:
MotorCmdPublisher()
: Node("motor_test_node")
{
publisher_ = this->create_publisher<MotorCmd>("/motor_cmd", 10);
//publisher_ = this->create_publisher<std_msgs::msg::String>("/led_cmd", 10);
cam_sub=this->create_subscription<sensor_msgs::msg::Image>("/camera/camera/color/image_raw",10,[=](sensor_msgs::msg::Image::SharedPtr img){
static double last_stamp;
double cur_stamp= log_printf("sub img:%dx%d ",img->width,img->height);
double diff=cur_stamp-last_stamp;
last_stamp=cur_stamp;
log_printf("hz:%.1f\n",1.0/diff);
});
//client = create_client<MotorInfo>("/motor_info");
///client = create_client<MotorParam>("/motor_param");
////timer_ = this->create_wall_timer(5000ms, std::bind(&MotorCmdPublisher::timer_callback, this));
}
private:
int angle1=9999;
int angle2=6666;
int xcnt=0;
//rclcpp::Client<interfaces::srv::MotorInfo>::SharedPtr client;
rclcpp::Client<interfaces::srv::MotorParam>::SharedPtr client;
rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr cam_sub;
void timer_callback()
{
auto request = std::make_shared<MotorParam::Request>();
request->motor_id=1;
request->max_speed=20;
request->add_acc=4;
request->dec_acc=5;
/*
auto future = client->async_send_request(request);
if (future.wait_for(5s) != std::future_status::ready) {
RCLCPP_ERROR(this->get_logger(), "服务调用超时");
return;
}
auto response=future.get();
printf("ret :%d\n",response->ret);
*/
using ServiceResponseFuture = rclcpp::Client<interfaces::srv::MotorParam>::SharedFuture;
auto response_received_callback = [this](ServiceResponseFuture future) {
auto response = future.get();
RCLCPP_INFO(this->get_logger(), "Service response: ret = %d", response->ret);
};
auto future = client->async_send_request(request, response_received_callback);
#if 0
auto request = std::make_shared<MotorInfo::Request>();
request->type="bm";
request->motor_ids.clear();
request->motor_ids.push_back(static_cast<uint8_t>(1));
request->motor_ids.push_back(static_cast<uint8_t>(2));
auto future = client->async_send_request(request);
if (future.wait_for(10s) != std::future_status::ready) {
RCLCPP_ERROR(this->get_logger(), "服务调用超时");
return;
}
auto response = future.get();
printf("ret angle:%.1f,%.1f\n",response->motor_angles[0],response->motor_angles[1]);
#endif
#if 0
auto message = MotorCmd();
message.target = "rs485";
message.type = "bm";
message.position = "";
message.motor_id.push_back(1);
message.motor_id.push_back(2);
idx+=1;
//angle1=angle1+20;
//angle2=angle2-20;
/*
if(idx%2==0){
}else{
angle1=-300;
angle2=300;
}
*/
message.motor_angle.push_back((float)angle1);
message.motor_angle.push_back((float)angle2);
publisher_->publish(message);
RCLCPP_INFO(this->get_logger(), "Published MotorCmd message:%d,%d",angle1,angle2);
#endif
#if 0
auto message=std_msgs::msg::String();
xcnt+=1;
if(xcnt%2==0)
message.data="green,10";
else
message.data="blue,10";
publisher_->publish(message);
RCLCPP_INFO(this->get_logger(), "Published LedCmd message:%s",message.data.c_str());
#endif
//timer_->cancel();
}
int idx=0;
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<MotorCmd>::SharedPtr publisher_;
//rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MotorCmdPublisher>());
rclcpp::shutdown();
return 0;
}

187
drv_test/src/xmain.cpp Normal file
View File

@@ -0,0 +1,187 @@
#include <chrono>
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "interfaces/msg/motor_cmd.hpp"
#include "interfaces/srv/motor_info.hpp"
#include "interfaces/srv/motor_param.hpp"
#include "std_msgs/msg/string.hpp"
#include "sensor_msgs/msg/image.hpp"
using namespace std::chrono_literals;
static auto g_program_start_time = std::chrono::steady_clock::now();
double log_printf(const char* format, ...) {
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::duration<double>>(now - g_program_start_time);
double seconds = elapsed.count();
double minute=seconds/60;
char buffer[1024];
va_list args;
va_start(args, format);
int n = vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
if (n < 0) {
std::fprintf(stderr, "[%.3f] (log error)\n", seconds);
return 0;
}
std::fprintf(stdout, "[%.3f/%.1f] %s", seconds,minute, buffer);
std::fflush(stdout);
return seconds;
}
using MotorCmd = interfaces::msg::MotorCmd;
using MotorInfo=interfaces::srv::MotorInfo;
using MotorParam=interfaces::srv::MotorParam;
class MotorCmdPublisher : public rclcpp::Node
{
public:
MotorCmdPublisher()
: Node("motor_test_node")
{
publisher_ = this->create_publisher<MotorCmd>("/motor_cmd", 10);
img_pub = this->create_publisher<sensor_msgs::msg::Image>("/color/image_raw",10);
//publisher_ = this->create_publisher<std_msgs::msg::String>("/led_cmd", 10);
cam_sub=this->create_subscription<sensor_msgs::msg::Image>("/camera/color/image_raw",10,[=](sensor_msgs::msg::Image::SharedPtr img){
static double last_stamp;
double cur_stamp= log_printf("sub img:%dx%d ",img->width,img->height);
double diff=cur_stamp-last_stamp;
last_stamp=cur_stamp;
log_printf("hz:%.1f\n",1.0/diff);
});
//client = create_client<MotorInfo>("/motor_info");
///client = create_client<MotorParam>("/motor_param");
timer_ = this->create_wall_timer(33ms, std::bind(&MotorCmdPublisher::timer_callback, this));
}
private:
int angle1=9999;
int angle2=6666;
int xcnt=0;
//rclcpp::Client<interfaces::srv::MotorInfo>::SharedPtr client;
rclcpp::Client<interfaces::srv::MotorParam>::SharedPtr client;
rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr cam_sub;
rclcpp::Publisher<sensor_msgs::msg::Image>::SharedPtr img_pub;
void timer_callback()
{
auto msg = std::make_unique<sensor_msgs::msg::Image>();
msg->header.stamp = this->now();
msg->header.frame_id = "camera_frame";
msg->height = 720;
msg->width = 1280;
msg->encoding = "rgb8"; // 3通道RGB
msg->is_bigendian = false;
msg->step = msg->width * 3; // 每行字节数 = 宽度 × 通道数
// 2. 填充随机数据 (1280x720x3 = 2,764,800字节)
msg->data.resize(msg->height * msg->step);
for (auto& pixel : msg->data) {
pixel = 23;//dist_(rng_);
}
// 3. 发布消息
img_pub->publish(std::move(msg));
static double last_stamp;
double cur_stamp=log_printf("pub msg:");
double diff=cur_stamp-last_stamp;
log_printf("hz:%.1f\n",1/diff);
last_stamp=cur_stamp;
return;
// auto request = std::make_shared<MotorParam::Request>();
// request->motor_id=1;
// request->max_speed=20;
// request->add_acc=4;
// request->dec_acc=5;
/*
auto future = client->async_send_request(request);
if (future.wait_for(5s) != std::future_status::ready) {
RCLCPP_ERROR(this->get_logger(), "服务调用超时");
return;
}
auto response=future.get();
printf("ret :%d\n",response->ret);
*/
// using ServiceResponseFuture = rclcpp::Client<interfaces::srv::MotorParam>::SharedFuture;
// auto response_received_callback = [this](ServiceResponseFuture future) {
// auto response = future.get();
// RCLCPP_INFO(this->get_logger(), "Service response: ret = %d", response->ret);
// };
// auto future = client->async_send_request(request, response_received_callback);
#if 0
auto request = std::make_shared<MotorInfo::Request>();
request->type="bm";
request->motor_ids.clear();
request->motor_ids.push_back(static_cast<uint8_t>(1));
request->motor_ids.push_back(static_cast<uint8_t>(2));
auto future = client->async_send_request(request);
if (future.wait_for(10s) != std::future_status::ready) {
RCLCPP_ERROR(this->get_logger(), "服务调用超时");
return;
}
auto response = future.get();
printf("ret angle:%.1f,%.1f\n",response->motor_angles[0],response->motor_angles[1]);
#endif
#if 0
auto message = MotorCmd();
message.target = "rs485";
message.type = "bm";
message.position = "";
message.motor_id.push_back(1);
message.motor_id.push_back(2);
idx+=1;
//angle1=angle1+20;
//angle2=angle2-20;
/*
if(idx%2==0){
}else{
angle1=-300;
angle2=300;
}
*/
message.motor_angle.push_back((float)angle1);
message.motor_angle.push_back((float)angle2);
publisher_->publish(message);
RCLCPP_INFO(this->get_logger(), "Published MotorCmd message:%d,%d",angle1,angle2);
#endif
#if 0
auto message=std_msgs::msg::String();
xcnt+=1;
if(xcnt%2==0)
message.data="green,10";
else
message.data="blue,10";
publisher_->publish(message);
RCLCPP_INFO(this->get_logger(), "Published LedCmd message:%s",message.data.c_str());
#endif
//timer_->cancel();
}
int idx=0;
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<MotorCmd>::SharedPtr publisher_;
//rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MotorCmdPublisher>());
rclcpp::shutdown();
return 0;
}

93
ecrt_dev/CMakeLists.txt Normal file
View File

@@ -0,0 +1,93 @@
cmake_minimum_required(VERSION 3.8)
project(ecrt_driver)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(hardware_interface REQUIRED)
find_package(pluginlib REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_lifecycle REQUIRED)
find_package(rclcpp_action REQUIRED)
find_package(trajectory_msgs REQUIRED)
find_package(control_msgs REQUIRED)
find_library(ECRT_LIB
NAMES ethercat ecrt
PATHS /opt/etherlab/lib /usr/local/lib /usr/lib
)
if(NOT ECRT_LIB)
message(FATAL_ERROR "ecrt not found; set CMAKE_PREFIX_PATH or system ld path.")
endif()
add_library(${PROJECT_NAME} SHARED src/ethercat_driver.cpp)
target_link_libraries(${PROJECT_NAME} ${ECRT_LIB} pthread)
target_include_directories(
${PROJECT_NAME}
PRIVATE
include
)
ament_target_dependencies(
${PROJECT_NAME}
hardware_interface
pluginlib
rclcpp
rclcpp_lifecycle
)
# 构建可执行文件
add_executable(test_motor src/test_motor.cpp)
ament_target_dependencies(test_motor
rclcpp
rclcpp_action
geometry_msgs
sensor_msgs
trajectory_msgs
control_msgs
)
install(TARGETS test_motor DESTINATION lib/${PROJECT_NAME})
# Causes the visibility macros to use dllexport rather than dllimport,
# which is appropriate when building the dll but not consuming it.
target_compile_definitions(${PROJECT_NAME} PRIVATE "ETHERCAT_DRIVER_BUILDING_LIBRARY")
# prevent pluginlib from using boost
target_compile_definitions(${PROJECT_NAME} PUBLIC "PLUGINLIB__DISABLE_BOOST_FUNCTIONS")
pluginlib_export_plugin_description_file(hardware_interface ethercat_driver_plugin.xml)
# INSTALL
install(
TARGETS ${PROJECT_NAME}
DESTINATION lib
)
install(
DIRECTORY include/
DESTINATION include
)
install(
DIRECTORY config description launch
DESTINATION share/${PROJECT_NAME}
)
if(BUILD_TESTING)
find_package(ament_cmake_gtest REQUIRED)
endif()
## EXPORTS
ament_export_include_directories(
include
)
ament_export_libraries(
${PROJECT_NAME}
)
ament_export_dependencies(
hardware_interface
pluginlib
rclcpp
rclcpp_lifecycle
ethercat_interface
)
ament_package()

View File

@@ -0,0 +1,142 @@
controller_manager:
ros__parameters:
update_rate: 1000 # Hz
joint_state_broadcaster:
type: joint_state_broadcaster/JointStateBroadcaster
trajectory_controller:
type: joint_trajectory_controller/JointTrajectoryController
gpio_command_controller:
type: gpio_controllers/GpioCommandController
trajectory_controller:
ros__parameters:
joints:
- joint_1
- joint_2
- joint_3
- joint_4
- joint_5
- joint_6
- joint_7
- joint_8
- joint_9
- joint_10
- joint_11
- joint_12
command_interfaces:
- position
state_interfaces:
- position
- velocity
state_publish_rate: 200.0
action_monitor_rate: 20.0
allow_partial_joints_goal: false
open_loop_control: true
allow_integration_in_goal_trajectories: true
constraints:
goal_time: 0.5
stopped_velocity_tolerance: 0.02
gpio_command_controller:
ros__parameters:
gpios:
- joint_1
- joint_2
- joint_3
- joint_4
- joint_5
- joint_6
- joint_7
- joint_8
- joint_9
- joint_10
- joint_11
- joint_12
command_interfaces:
joint_1:
- interfaces:
- fault
- enable
joint_2:
- interfaces:
- fault
- enable
joint_3:
- interfaces:
- fault
- enable
joint_4:
- interfaces:
- fault
- enable
joint_5:
- interfaces:
- fault
- enable
joint_6:
- interfaces:
- fault
- enable
joint_7:
- interfaces:
- fault
- enable
joint_8:
- interfaces:
- fault
- enable
joint_9:
- interfaces:
- fault
- enable
joint_10:
- interfaces:
- fault
- enable
joint_11:
- interfaces:
- fault
- enable
joint_12:
- interfaces:
- fault
- enable
state_interfaces:
joint_1:
- interfaces:
- fault
joint_2:
- interfaces:
- fault
joint_3:
- interfaces:
- fault
joint_4:
- interfaces:
- fault
joint_5:
- interfaces:
- fault
joint_6:
- interfaces:
- fault
joint_7:
- interfaces:
- fault
joint_8:
- interfaces:
- fault
joint_9:
- interfaces:
- fault
joint_10:
- interfaces:
- fault
joint_11:
- interfaces:
- fault
joint_12:
- interfaces:
- fault

View File

@@ -0,0 +1,106 @@
controller_manager:
ros__parameters:
update_rate: 1000 # Hz
joint_state_broadcaster:
type: joint_state_broadcaster/JointStateBroadcaster
trajectory_controller:
type: joint_trajectory_controller/JointTrajectoryController
gpio_command_controller:
type: gpio_controllers/GpioCommandController
trajectory_controller:
ros__parameters:
joints:
- joint_1
- joint_2
- joint_3
- joint_4
- joint_5
- joint_6
- joint_7
- joint_8
command_interfaces:
- position
state_interfaces:
- position
- velocity
state_publish_rate: 200.0
action_monitor_rate: 20.0
allow_partial_joints_goal: false
open_loop_control: true
allow_integration_in_goal_trajectories: true
constraints:
goal_time: 0.5
stopped_velocity_tolerance: 0.02
gpio_command_controller:
ros__parameters:
gpios:
- joint_1
- joint_2
- joint_3
- joint_4
- joint_5
- joint_6
- joint_7
- joint_8
command_interfaces:
joint_1:
- interfaces:
- fault
- enable
joint_2:
- interfaces:
- fault
- enable
joint_3:
- interfaces:
- fault
- enable
joint_4:
- interfaces:
- fault
- enable
joint_5:
- interfaces:
- fault
- enable
joint_6:
- interfaces:
- fault
- enable
joint_7:
- interfaces:
- fault
- enable
joint_8:
- interfaces:
- fault
- enable
state_interfaces:
joint_1:
- interfaces:
- fault
joint_2:
- interfaces:
- fault
joint_3:
- interfaces:
- fault
joint_4:
- interfaces:
- fault
joint_5:
- interfaces:
- fault
joint_6:
- interfaces:
- fault
joint_7:
- interfaces:
- fault
joint_8:
- interfaces:
- fault

View File

@@ -0,0 +1,34 @@
# Configuration file for Maxon EPOS3 drive
vendor_id: 0x5a65726f
product_id: 0x00029252
assign-activate: 0x0300 #DC synch register
auto_fault_reset: true
auto_enable_set: true
auto_state_transitions: true
sdo: # sdo data to be transferred at drive startup
- {index: 0x6060, sub_index: 0, type: uint16, value: 8} # Set 0x1C33:01h = 0x02 DC设置
#- {index: 0x6040, sub_index: 0, type: uint16, value: 0x80} # 清除错误
#- {index: 0x6040, sub_index: 0, type: uint16, value: 0}
rpdo: #PxPDO = receive PDO Mapping
- index: 0x1600
channels:
- {index: 0x607a, sub_index: 0, type: int32, command_interface: position, default: .nan, factor: 1.0, offset: 0} #target position
# - {index: 0x60ff, sub_index: 0, type: int32, default: 0, factor: 1.0} #target velocity
# - {index: 0x6071, sub_index: 0, type: int16, default: 0, factor: 1.0} #target torque
# - {index: 0x6072, sub_index: 0, type: uint16, default: 500} # Max torque
- {index: 0x6040, sub_index: 0, type: uint16, default: 0} # Control word
- {index: 0x6060, sub_index: 0, type: uint8, default: 8} # Control operation
- {index: 0xf0ff, sub_index: 0, type: uint8} # Dummy byte
tpdo: #TxPDO = transmit PDO Mapping
- index: 0x1a00
channels:
- {index: 0x6064, sub_index: 0, type: int32, state_interface: position, factor: 1.0, offset: 0} #Position actual value
# - {index: 0x60F4, sub_index: 0, type: int32} #Position Following error actual value
- {index: 0x606c, sub_index: 0, type: int32, state_interface: velocity, factor: 1.0 } # Velocity actual value
# - {index: 0x6077, sub_index: 0, type: int16, state_interface: effort, factor: 1.0 } # Torque actual value
- {index: 0x6041, sub_index: 0, type: uint16} # State word
# - {index: 0x603f, sub_index: 0, type: uint16} # Error code
- {index: 0x6061, sub_index: 0, type: uint8, state_interface: mode_of_operation} # Mode of operation display
- {index: 0xf0ff, sub_index: 0, type: uint8} # dummy byte

View File

@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="motor_drive">
<link name="world"/>
<xacro:include filename="motor_drive.ros2_control.xacro" />
<xacro:motor_drive/>
</robot>

View File

@@ -0,0 +1,62 @@
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<!-- 公共参数定义(一次修改,全局生效) -->
<xacro:property name="master_id" value="0" />
<xacro:property name="control_frequency" value="10" />
<xacro:property name="ec_plugin" value="ethercat_generic_plugins/EcCiA402Drive" />
<xacro:property name="alias" value="0" />
<xacro:property name="mode_of_operation" value="8" />
<xacro:property name="slave_config_path" value="config/zeroErr_config.yaml" />
<!-- 子宏:封装单个关节的重复配置 -->
<!-- 参数joint_name关节名称、ec_positionEtherCAT位置索引 -->
<xacro:macro name="single_joint_config" params="joint_name ec_position">
<joint name="${joint_name}">
<!-- 状态接口(位置/速度/力矩) -->
<state_interface name="position"/>
<state_interface name="velocity"/>
<state_interface name="fault"/>
<!-- 命令接口(位置控制/故障重置/使能) -->
<command_interface name="fault"/>
<command_interface name="enable"/>
<command_interface name="position"/>
<!-- EtherCAT模块配置复用公共参数 -->
<ec_module name="zeroErr">
<plugin>${ec_plugin}</plugin>
<param name="alias">${alias}</param>
<param name="position">${ec_position}</param>
<param name="mode_of_operation">${mode_of_operation}</param>
<param name="slave_config">${slave_config_path}</param>
</ec_module>
</joint>
</xacro:macro>
<!-- 主宏:电机驱动系统配置 -->
<xacro:macro name="motor_drive">
<ros2_control name="motor_drive" type="system">
<hardware>
<plugin>ethercat_driver/EthercatDriver</plugin>
<param name="master_id">${master_id}</param>
<param name="control_frequency">${control_frequency}</param>
</hardware>
<!-- 调用子宏生成多个关节(仅需指定名称和位置索引) -->
<xacro:single_joint_config joint_name="joint_1" ec_position="1" />
<xacro:single_joint_config joint_name="joint_2" ec_position="2" />
<xacro:single_joint_config joint_name="joint_3" ec_position="3" />
<xacro:single_joint_config joint_name="joint_4" ec_position="4" />
<xacro:single_joint_config joint_name="joint_5" ec_position="5" />
<xacro:single_joint_config joint_name="joint_6" ec_position="6" />
<xacro:single_joint_config joint_name="joint_7" ec_position="7" />
<xacro:single_joint_config joint_name="joint_8" ec_position="8" />
<xacro:single_joint_config joint_name="joint_9" ec_position="9" />
<xacro:single_joint_config joint_name="joint_10" ec_position="10" />
<xacro:single_joint_config joint_name="joint_11" ec_position="11" />
<xacro:single_joint_config joint_name="joint_12" ec_position="12" />
</ros2_control>
</xacro:macro>
</robot>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="motor_drive">
<link name="world"/>
<xacro:include filename="r1_motor_drive.ros2_control.xacro" />
<xacro:motor_drive/>
</robot>

View File

@@ -0,0 +1,58 @@
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<!-- 公共参数定义(一次修改,全局生效) -->
<xacro:property name="master_id" value="0" />
<xacro:property name="control_frequency" value="10" />
<xacro:property name="ec_plugin" value="ethercat_generic_plugins/EcCiA402Drive" />
<xacro:property name="alias" value="0" />
<xacro:property name="mode_of_operation" value="8" />
<xacro:property name="slave_config_path" value="config/zeroErr_config.yaml" />
<!-- 子宏:封装单个关节的重复配置 -->
<!-- 参数joint_name关节名称、ec_positionEtherCAT位置索引 -->
<xacro:macro name="single_joint_config" params="joint_name ec_position">
<joint name="${joint_name}">
<!-- 状态接口(位置/速度/力矩) -->
<state_interface name="position"/>
<state_interface name="velocity"/>
<state_interface name="fault"/>
<!-- 命令接口(位置控制/故障重置/使能) -->
<command_interface name="fault"/>
<command_interface name="enable"/>
<command_interface name="position"/>
<!-- EtherCAT模块配置复用公共参数 -->
<ec_module name="zeroErr">
<plugin>${ec_plugin}</plugin>
<param name="alias">${alias}</param>
<param name="position">${ec_position}</param>
<param name="mode_of_operation">${mode_of_operation}</param>
<param name="slave_config">${slave_config_path}</param>
</ec_module>
</joint>
</xacro:macro>
<!-- 主宏:电机驱动系统配置 -->
<xacro:macro name="motor_drive">
<ros2_control name="motor_drive" type="system">
<hardware>
<plugin>ethercat_driver/EthercatDriver</plugin>
<param name="master_id">${master_id}</param>
<param name="control_frequency">${control_frequency}</param>
</hardware>
<!-- 调用子宏生成多个关节(仅需指定名称和位置索引) -->
<xacro:single_joint_config joint_name="joint_1" ec_position="1" />
<xacro:single_joint_config joint_name="joint_2" ec_position="2" />
<xacro:single_joint_config joint_name="joint_3" ec_position="3" />
<xacro:single_joint_config joint_name="joint_4" ec_position="4" />
<xacro:single_joint_config joint_name="joint_5" ec_position="5" />
<xacro:single_joint_config joint_name="joint_6" ec_position="6" />
<xacro:single_joint_config joint_name="joint_7" ec_position="7" />
<xacro:single_joint_config joint_name="joint_8" ec_position="8" />
</ros2_control>
</xacro:macro>
</robot>

View File

@@ -0,0 +1,9 @@
<library path="ecrt_driver">
<class name="ethercat_driver/EthercatDriver"
type="ethercat_driver::EthercatDriver"
base_class_type="hardware_interface::SystemInterface">
<description>
EtherCAT Driver for ros2_control.
</description>
</class>
</library>

View File

@@ -0,0 +1,141 @@
// Copyright 2022 ICUBE Laboratory, University of Strasbourg
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef ETHERCAT_DRIVER__ETHERCAT_DRIVER_HPP_
#define ETHERCAT_DRIVER__ETHERCAT_DRIVER_HPP_
#include<thread>
#include <unordered_map>
#include <memory>
#include <string>
#include <vector>
#include <array>
#include <atomic>
#include <pluginlib/class_loader.hpp>
#include "hardware_interface/handle.hpp"
#include "hardware_interface/hardware_info.hpp"
#include "hardware_interface/system_interface.hpp"
#include "hardware_interface/types/hardware_interface_return_values.hpp"
#include "rclcpp/macros.hpp"
#include "rclcpp_lifecycle/node_interfaces/lifecycle_node_interface.hpp"
#include "rclcpp_lifecycle/state.hpp"
#include "ethercat_driver/visibility_control.h"
//#include "ethercat_interface/ec_slave.hpp"
//#include "ethercat_interface/ec_master.hpp"
#include "zer_config.hpp"
using CallbackReturn = rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn;
#define CYCLIC_POSITION 8 //CSP 周期同步位置模式
#define CYCLIC_VELOCITY 9 //CSP 周期同步速度模式
#define CYCLIC_TORQUE 10 //CSP 周期同步扭矩模式
#define PVT_MODE 5 //PVT模式
namespace ethercat_driver
{
class EthercatDriver : public hardware_interface::SystemInterface
{
public:
RCLCPP_SHARED_PTR_DEFINITIONS(EthercatDriver)
ETHERCAT_DRIVER_PUBLIC
CallbackReturn on_init(const hardware_interface::HardwareInfo & info) override;
ETHERCAT_DRIVER_PUBLIC
CallbackReturn on_configure(const rclcpp_lifecycle::State & previous_state) override;
ETHERCAT_DRIVER_PUBLIC
std::vector<hardware_interface::StateInterface> export_state_interfaces() override;
ETHERCAT_DRIVER_PUBLIC
std::vector<hardware_interface::CommandInterface> export_command_interfaces() override;
ETHERCAT_DRIVER_PUBLIC
CallbackReturn on_activate(const rclcpp_lifecycle::State & previous_state) override;
ETHERCAT_DRIVER_PUBLIC
CallbackReturn on_deactivate(const rclcpp_lifecycle::State & previous_state) override;
ETHERCAT_DRIVER_PUBLIC
hardware_interface::return_type read(const rclcpp::Time &, const rclcpp::Duration &) override;
ETHERCAT_DRIVER_PUBLIC
hardware_interface::return_type write(const rclcpp::Time &, const rclcpp::Duration &) override;
void readData();
void writeData();
void check_master_state();
void check_domain1_state();
void check_slave_config_states();
void set_motor_enable(int id,bool enable){
if(id>0&&id<9){
motor_enable_arr[id-1].store(enable);
}
};
bool get_motor_enable(int id){
if(id>0&&id<9){
return motor_enable_arr[id-1].load();
}
return false;
}
struct timespec timespec_add(struct timespec time1, struct timespec time2);
private:
std::array<std::atomic<bool>, NUM_SLAVES> motor_enable_arr;
std::vector<std::unordered_map<std::string, std::string>> getEcModuleParam(
std::string & urdf, std::string component_name, std::string component_type);
///std::vector<std::shared_ptr<ethercat_interface::EcSlave>> ec_modules_;
std::vector<std::unordered_map<std::string, std::string>> ec_module_parameters_;
std::vector<std::vector<double>> hw_joint_commands_;
std::vector<double> hw_cmd_position_;
std::vector<std::vector<double>> hw_sensor_commands_;
std::vector<std::vector<double>> hw_gpio_commands_;
std::vector<std::vector<double>> hw_joint_states_;
std::vector<std::vector<double>> hw_sensor_states_;
std::vector<std::vector<double>> hw_gpio_states_;
//pluginlib::ClassLoader<ethercat_interface::EcSlave> ec_loader_{"ethercat_interface", "ethercat_interface::EcSlave"};
struct timespec wakeupTime,time;
int control_frequency_;
///ethercat_interface::EcMaster master_;
ec_master_t *master = NULL;
ec_domain_t *domain1 = NULL;
ec_slave_config_t *sc[NUM_SLAVES] = {0};
uint8_t *domain1_pd = NULL;
ec_domain_state_t domain1_state = {};
ec_master_state_t master_state = {};
ec_slave_config_state_t sc_state[NUM_SLAVES] = {};
std::mutex ec_mutex_;
bool activated_;
#define FREQUENCY 1000
#define NSEC_PER_SEC (1000000000L)
#define CSP_MAX_VEL_COUNTS_PER_S 65536
#define CSP_POS_DEADBAND 10 //CSP允许的误差(计数)
#define CLOCK_TO_USE CLOCK_MONOTONIC
#define NSEC_PER_SEC (1000000000L)
#define PERIOD_NS (NSEC_PER_SEC / FREQUENCY) //本次设置周期PERIOD_NS为1ms
#define DIFF_NS(A, B) (((B).tv_sec - (A).tv_sec) * NSEC_PER_SEC + (B).tv_nsec - (A).tv_nsec)
#define TIMESPEC2NS(T) ((uint64_t) (T).tv_sec * NSEC_PER_SEC + (T).tv_nsec)
const struct timespec cycletime = {0, PERIOD_NS};
uint16_t command[NUM_SLAVES]; //状态字掩码
uint16_t status[NUM_SLAVES]; //状态字
uint16_t last_status[NUM_SLAVES]; //上个循环的状态字
int8_t last_mode_cmd[NUM_SLAVES] = {CYCLIC_VELOCITY};
int8_t mode_cmd=8;
int inited = 0; //初始化
unsigned int counter = 0;
unsigned int sync_ref_counter = 0;
};
} // namespace ethercat_driver
#endif // ETHERCAT_DRIVER__ETHERCAT_DRIVER_HPP_

View File

@@ -0,0 +1,49 @@
// Copyright 2022 ICUBE Laboratory, University of Strasbourg
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef ETHERCAT_DRIVER__VISIBILITY_CONTROL_H_
#define ETHERCAT_DRIVER__VISIBILITY_CONTROL_H_
// This logic was borrowed (then namespaced) from the examples on the gcc wiki:
// https://gcc.gnu.org/wiki/Visibility
#if defined _WIN32 || defined __CYGWIN__
#ifdef __GNUC__
#define ETHERCAT_DRIVER_EXPORT __attribute__ ((dllexport))
#define ETHERCAT_DRIVER_IMPORT __attribute__ ((dllimport))
#else
#define ETHERCAT_DRIVER_EXPORT __declspec(dllexport)
#define ETHERCAT_DRIVER_IMPORT __declspec(dllimport)
#endif
#ifdef ETHERCAT_DRIVER_BUILDING_LIBRARY
#define ETHERCAT_DRIVER_PUBLIC ETHERCAT_DRIVER_EXPORT
#else
#define ETHERCAT_DRIVER_PUBLIC ETHERCAT_DRIVER_IMPORT
#endif
#define ETHERCAT_DRIVER_PUBLIC_TYPE ETHERCAT_DRIVER_PUBLIC
#define ETHERCAT_DRIVER_LOCAL
#else
#define ETHERCAT_DRIVER_EXPORT __attribute__ ((visibility("default")))
#define ETHERCAT_DRIVER_IMPORT
#if __GNUC__ >= 4
#define ETHERCAT_DRIVER_PUBLIC __attribute__ ((visibility("default")))
#define ETHERCAT_DRIVER_LOCAL __attribute__ ((visibility("hidden")))
#else
#define ETHERCAT_DRIVER_PUBLIC
#define ETHERCAT_DRIVER_LOCAL
#endif
#define ETHERCAT_DRIVER_PUBLIC_TYPE
#endif
#endif // ETHERCAT_DRIVER__VISIBILITY_CONTROL_H_

View File

@@ -0,0 +1,94 @@
#include "ecrt.h"
#include <math.h>
#define NUM_SLAVES 8
#define ZER_VID_PID 0x5a65726f,0x00029252
typedef struct {
unsigned int ctrl_word; // 0x6040:00
unsigned int target_position; // 0x607A:00
unsigned int target_velocity; // 0x60FF:00
unsigned int target_torque; // 0x6071:00
unsigned int max_torque; // 0x6072:00
unsigned int operation_mode; // 0x6060:00
unsigned int reserved1; // 0xf0ff:00
unsigned int status_word; // 0x6041:00
unsigned int position_actual_value; // 0x6064:00
unsigned int velocity_actual_value; // 0x606C:00
unsigned int torque_actual_value; // 0x6077:00
unsigned int error_code; // 0x603F:00
unsigned int modes_of_operation_display; // 0x6061:00
unsigned int reserved2; // 0xf0ff:00
} zer_offset_t;
static zer_offset_t zer_offsets[12];
constexpr double rad_to_count= 524288.0/(2*M_PI);
constexpr double count_to_rad=2*M_PI/524288.0;
#define PDO_ENTRY(alias,position,index) \
{alias,position, ZER_VID_PID, 0x6040, 0, &zer_offsets[index].ctrl_word,NULL}, \
{alias,position, ZER_VID_PID, 0x607A, 0, &zer_offsets[index].target_position,NULL},\
{alias,position, ZER_VID_PID, 0x60FF, 0, &zer_offsets[index].target_velocity,NULL},\
{alias,position, ZER_VID_PID, 0x6071, 0, &zer_offsets[index].target_torque,NULL},\
{alias,position, ZER_VID_PID, 0x6072, 0, &zer_offsets[index].max_torque,NULL},\
{alias,position, ZER_VID_PID, 0x6060, 0, &zer_offsets[index].operation_mode,NULL},\
{alias,position, ZER_VID_PID, 0xf0ff, 0, &zer_offsets[index].reserved1,NULL},\
{alias,position, ZER_VID_PID, 0x6041, 0, &zer_offsets[index].status_word,NULL},\
{alias,position, ZER_VID_PID, 0x6064, 0, &zer_offsets[index].position_actual_value,NULL},\
{alias,position, ZER_VID_PID, 0x606C, 0, &zer_offsets[index].velocity_actual_value,NULL},\
{alias,position, ZER_VID_PID, 0x6077, 0, &zer_offsets[index].torque_actual_value,NULL},\
{alias,position, ZER_VID_PID, 0x603F, 0, &zer_offsets[index].error_code,NULL},\
{alias,position, ZER_VID_PID, 0x6061, 0, &zer_offsets[index].modes_of_operation_display,NULL},\
{alias,position, ZER_VID_PID, 0xf0ff, 0, &zer_offsets[index].reserved2,NULL},
// ------------------- PDO 定义CSV/CSP/CST对应 0x1600/0x1A00 -------------------
// 下列结构由IGH主站 cstruct 自动生成
const static ec_pdo_entry_reg_t zer_domain1_regs[] = {
PDO_ENTRY(0,1,0)
PDO_ENTRY(0,2,1)
PDO_ENTRY(0,3,2)
PDO_ENTRY(0,4,3)
PDO_ENTRY(0,5,4)
PDO_ENTRY(0,6,5)
PDO_ENTRY(0,7,6)
PDO_ENTRY(0,8,7)
PDO_ENTRY(0,9,8)
PDO_ENTRY(0,10,9)
PDO_ENTRY(0,11,10)
PDO_ENTRY(0,12,11)
{} // 结束标记
};
static ec_pdo_entry_info_t zer_device_pdo_entries[] = {
/*RxPdo 0x1600*/
{0x6040, 0x00, 16}, /* control word */
{0x607a, 0x00, 32}, /* target position */
{0x60ff, 0x00, 32}, /* target velocity */
{0x6071, 0x00, 16}, /* Target Torque */
{0x6072, 0x00, 16}, /* Max Torque */
{0x6060, 0x00, 8}, /* modes of operation */
{0xf0ff, 0x00, 8},
/*TxPdo 0x1A00*/
{0x6041, 0x00, 16}, /* status word */
{0x6064, 0x00, 32}, /* position actual value */
{0x606c, 0x00, 32}, /* velocity actual value */
{0x6077, 0x00, 16}, /* torque actual value */
{0x603f, 0x00, 16}, /* error code */
{0x6061, 0x00, 8}, /* modes of operation display */
{0xf0ff, 0x00, 8},
};
static ec_pdo_info_t zer_device_pdos[] = {
//RxPdo
{0x1600, 7, zer_device_pdo_entries + 0 },
//TxPdo
{0x1A00, 7, zer_device_pdo_entries + 7 }
};
static ec_sync_info_t zer_device_syncs[] = {
{0, EC_DIR_OUTPUT, 0, NULL, EC_WD_DISABLE},
{1, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
{2, EC_DIR_OUTPUT, 1, zer_device_pdos + 0, EC_WD_ENABLE},
{3, EC_DIR_INPUT, 1, zer_device_pdos + 1, EC_WD_DISABLE},
{0xff}
};

View File

@@ -0,0 +1,95 @@
from launch import LaunchDescription
from launch.substitutions import Command, FindExecutable, PathJoinSubstitution, LaunchConfiguration, TextSubstitution
from launch.actions import DeclareLaunchArgument,TimerAction
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare
def generate_launch_description():
# Declare arguments
declared_arguments = []
declared_arguments.append(
DeclareLaunchArgument(
'description_file',
default_value='motor_drive.config.xacro',
description='URDF/XACRO description file with the axis.',
)
)
description_file = LaunchConfiguration('description_file')
# Get URDF via xacro
robot_description_content = Command(
[
PathJoinSubstitution([FindExecutable(name="xacro")]),
" ",
PathJoinSubstitution(
[
FindPackageShare("ecrt_driver"),
"description",
description_file,
]
),
]
)
robot_description = {"robot_description": robot_description_content}
robot_controllers = PathJoinSubstitution(
[
FindPackageShare("ecrt_driver"),
"config",
"controllers.yaml",
]
)
control_node = Node(
package="controller_manager",
executable="ros2_control_node",
parameters=[robot_description, robot_controllers,
{
"lock_memory": True,
"thread_priority": 90
}
],
output="both",
)
robot_state_pub_node = Node(
package="robot_state_publisher",
executable="robot_state_publisher",
parameters=[robot_description],
output='screen'
)
joint_state_broadcaster_spawner = Node(
package="controller_manager",
executable="spawner",
arguments=["joint_state_broadcaster", "-c", "/controller_manager"],
)
gpio_controller_spawner = Node(
package="controller_manager",
executable="spawner",
arguments=["gpio_command_controller", "-c", "/controller_manager"],
)
trajectory_controller_spawner = Node(
package="controller_manager",
executable="spawner",
arguments=["trajectory_controller", "-c", "/controller_manager"],
)
delay_node=TimerAction(period=1.0,actions=[gpio_controller_spawner,trajectory_controller_spawner])
nodes = [
control_node,
robot_state_pub_node,
joint_state_broadcaster_spawner,
delay_node,
]#position_controller_spawner,
return LaunchDescription(
declared_arguments +
nodes)

View File

@@ -0,0 +1,144 @@
#!/usr/bin/env python3
from launch import LaunchDescription
# 补全必要导入,增强跨设备兼容性
from launch.substitutions import (
Command, FindExecutable, PathJoinSubstitution,
LaunchConfiguration, TextSubstitution, EnvironmentVariable
)
from launch.actions import (
DeclareLaunchArgument, TimerAction, OpaqueFunction
)
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare
# 新增:导入系统路径模块,避免路径解析异常
import os
def generate_launch_description():
# -------------------------- 1. 声明参数(增强默认值和兼容性) --------------------------
declared_arguments = []
declared_arguments.append(
DeclareLaunchArgument(
'description_file',
default_value=TextSubstitution(text='motor_drive.config.xacro'), # 显式用TextSubstitution避免解析歧义
description='URDF/XACRO description file with the axis.',
)
)
# 新增:声明工作空间路径参数,适配不同设备的路径差异
declared_arguments.append(
DeclareLaunchArgument(
'pkg_share_path',
default_value=FindPackageShare("ecrt_driver"),
description='Path to ecrt_driver package share directory'
)
)
# 绑定参数,规范命名
description_file = LaunchConfiguration('description_file')
pkg_share_path = LaunchConfiguration('pkg_share_path')
# -------------------------- 2. 重构URDF生成逻辑解决拼接解析异常 --------------------------
# 优化用TextSubstitution替换纯空格避免跨设备解析时的空格截断问题
robot_description_content = Command(
[
FindExecutable(name="xacro"),
TextSubstitution(text=" "), # 替换原纯空格规范Substitution拼接
PathJoinSubstitution(
[
pkg_share_path, # 使用声明的参数,增强兼容性
TextSubstitution(text="description"),
description_file,
]
),
TextSubstitution(text=" "), # 确保xacro命令参数分隔清晰
]
)
robot_description = {"robot_description": robot_description_content}
# -------------------------- 3. 重构控制器配置路径(避免路径解析失败) --------------------------
robot_controllers = PathJoinSubstitution(
[
pkg_share_path,
TextSubstitution(text="config"),
TextSubstitution(text="controllers.yaml"),
]
)
# -------------------------- 4. 节点配置(增强跨设备兼容性) --------------------------
# 控制节点添加emulate_tty=True修复日志输出和权限问题参数格式规范化
control_node = Node(
package="controller_manager",
executable="ros2_control_node", # 确保executable为纯字符串无替换对象
parameters=[
robot_description,
robot_controllers,
{
"lock_memory": True,
"thread_priority": 90,
"use_sim_time": False # 新增:显式关闭仿真时间,避免环境歧义
}
],
output="both",
emulate_tty=True, # 关键:修复跨设备终端输出解析问题
namespace="/ecrt_driver" # 新增:添加命名空间,避免节点冲突
)
# 机器人状态发布节点:优化输出和参数
robot_state_pub_node = Node(
package="robot_state_publisher",
executable="robot_state_publisher", # 纯字符串,无替换对象
parameters=[
robot_description,
{"use_sim_time": False} # 显式指定,增强兼容性
],
output='screen',
emulate_tty=True, # 修复TextSubstitution相关的输出解析问题
namespace="/ecrt_driver"
)
# 关节状态广播器:参数格式规范化
joint_state_broadcaster_spawner = Node(
package="controller_manager",
executable="spawner", # 纯字符串,无替换对象
arguments=["joint_state_broadcaster", "-c", "/controller_manager"],
output='screen',
emulate_tty=True,
namespace="/ecrt_driver"
)
# GPIO控制器参数格式规范化
gpio_controller_spawner = Node(
package="controller_manager",
executable="spawner", # 纯字符串,无替换对象
arguments=["gpio_command_controller", "-c", "/controller_manager"],
output='screen',
emulate_tty=True,
namespace="/ecrt_driver"
)
# 轨迹控制器:参数格式规范化
trajectory_controller_spawner = Node(
package="controller_manager",
executable="spawner", # 纯字符串,无替换对象
arguments=["trajectory_controller", "-c", "/controller_manager"],
output='screen',
emulate_tty=True,
namespace="/ecrt_driver"
)
# -------------------------- 5. 延时节点(修复执行顺序解析问题) --------------------------
# 优化TimerAction的actions用列表包裹避免解析时的类型歧义
delay_node = TimerAction(
period=1.0,
actions=[gpio_controller_spawner, trajectory_controller_spawner],
)
# 组装所有节点
nodes = [
control_node,
robot_state_pub_node,
joint_state_broadcaster_spawner,
delay_node,
]
# 返回LaunchDescription确保参数声明和节点列表拼接规范
return LaunchDescription(declared_arguments + nodes)

View File

@@ -0,0 +1,88 @@
from launch import LaunchDescription
from launch.substitutions import Command, FindExecutable, PathJoinSubstitution, LaunchConfiguration
from launch.actions import DeclareLaunchArgument,TimerAction
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare
def generate_launch_description():
# Declare arguments
declared_arguments = []
declared_arguments.append(
DeclareLaunchArgument(
'description_file',
default_value='r1_motor_drive.config.xacro',
description='URDF/XACRO description file with the axis.',
)
)
description_file = LaunchConfiguration('description_file')
# Get URDF via xacro
robot_description_content = Command(
[
PathJoinSubstitution([FindExecutable(name="xacro")]),
" ",
PathJoinSubstitution(
[
FindPackageShare("ecrt_driver"),
"description",
description_file,
]
),
]
)
robot_description = {"robot_description": robot_description_content}
robot_controllers = PathJoinSubstitution(
[
FindPackageShare("ecrt_driver"),
"config",
"r1controllers.yaml",
]
)
control_node = Node(
package="controller_manager",
executable="ros2_control_node",
parameters=[robot_description, robot_controllers,
{
"lock_memory": True,
"thread_priority": 90
}
],
output="both",
)
print('444444')
robot_state_pub_node = Node(
package="robot_state_publisher",
executable="robot_state_publisher",
parameters=[robot_description],
output='screen'
)
joint_state_broadcaster_spawner = Node(
package="controller_manager",
executable="spawner",
arguments=["joint_state_broadcaster", "-c", "/controller_manager"],
)
gpio_controller_spawner = Node(
package="controller_manager",
executable="spawner",
arguments=["gpio_command_controller", "-c", "/controller_manager"],
)
trajectory_controller_spawner = Node(
package="controller_manager",
executable="spawner",
arguments=["trajectory_controller", "-c", "/controller_manager"],
)
delay_node=TimerAction(period=1.0,actions=[gpio_controller_spawner,trajectory_controller_spawner])
nodes = [
control_node,
robot_state_pub_node,
joint_state_broadcaster_spawner,
delay_node,
]#position_controller_spawner,
return LaunchDescription(
declared_arguments +
nodes)

25
ecrt_dev/package.xml Normal file
View File

@@ -0,0 +1,25 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>ecrt_driver</name>
<version>1.2.0</version>
<description>EtherCAT Driver for `ros2_control`</description>
<maintainer email="mcbed.robotics@gamil.com">Maciej Bednarczyk</maintainer>
<license>Apache-2.0</license>
<buildtool_depend>ament_cmake_ros</buildtool_depend>
<depend>hardware_interface</depend>
<depend>pluginlib</depend>
<depend>rclcpp</depend>
<depend>rclcpp_lifecycle</depend>
<depend>ethercat_interface</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>

View File

@@ -0,0 +1,818 @@
#include "ethercat_driver/ethercat_driver.hpp"
#include <tinyxml2.h>
#include <string>
#include <regex>
#include"ecrt.h"
#include "hardware_interface/types/hardware_interface_type_values.hpp"
#include "rclcpp/rclcpp.hpp"
namespace ethercat_driver
{
CallbackReturn EthercatDriver::on_init(
const hardware_interface::HardwareInfo & info)
{
if (hardware_interface::SystemInterface::on_init(info) != CallbackReturn::SUCCESS) {
return CallbackReturn::ERROR;
}
const std::lock_guard<std::mutex> lock(ec_mutex_);
activated_ = false;
for(int i=0;i<NUM_SLAVES;i++){
set_motor_enable(i+1,false);
}
hw_joint_states_.resize(info_.joints.size());
for (uint j = 0; j < info_.joints.size(); j++) {
hw_joint_states_[j].resize(
info_.joints[j].state_interfaces.size(),
std::numeric_limits<double>::quiet_NaN());
}
hw_sensor_states_.resize(info_.sensors.size());
for (uint s = 0; s < info_.sensors.size(); s++) {
hw_sensor_states_[s].resize(
info_.sensors[s].state_interfaces.size(),
std::numeric_limits<double>::quiet_NaN());
}
hw_gpio_states_.resize(info_.gpios.size());
for (uint g = 0; g < info_.gpios.size(); g++) {
hw_gpio_states_[g].resize(
info_.gpios[g].state_interfaces.size(),
std::numeric_limits<double>::quiet_NaN());
}
hw_cmd_position_.resize(info_.joints.size());
#if 1
hw_joint_commands_.resize(info_.joints.size());
for (uint j = 0; j < info_.joints.size(); j++) {
hw_joint_commands_[j].resize(
info_.joints[j].command_interfaces.size(),
std::numeric_limits<double>::quiet_NaN());
}
#endif
hw_sensor_commands_.resize(info_.sensors.size());
for (uint s = 0; s < info_.sensors.size(); s++) {
hw_sensor_commands_[s].resize(
info_.sensors[s].command_interfaces.size(),
std::numeric_limits<double>::quiet_NaN());
}
hw_gpio_commands_.resize(info_.gpios.size());
for (uint g = 0; g < info_.gpios.size(); g++) {
hw_gpio_commands_[g].resize(
info_.gpios[g].command_interfaces.size(),
std::numeric_limits<double>::quiet_NaN());
}
RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"), "joints.size:%d,%d,%d",info_.joints.size(),info_.joints[0].state_interfaces.size(),info_.joints[1].state_interfaces.size());
for (uint j = 0; j < info_.joints.size(); j++) {
//RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"), "joints");
// check all joints for EC modules and load into ec_modules_
auto module_params = getEcModuleParam(info_.original_xml, info_.joints[j].name, "joint");
ec_module_parameters_.insert(
ec_module_parameters_.end(), module_params.begin(), module_params.end());
for (auto i = 0ul; i < module_params.size(); i++) {
for (auto k = 0ul; k < info_.joints[j].state_interfaces.size(); k++) {
module_params[i]["state_interface/" +
info_.joints[j].state_interfaces[k].name] = std::to_string(k);
}
for (auto k = 0ul; k < info_.joints[j].command_interfaces.size(); k++) {
module_params[i]["command_interface/" +
info_.joints[j].command_interfaces[k].name] = std::to_string(k);
}
}
}
RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"), "gpios.size:%d",info_.gpios.size());
for (uint g = 0; g < info_.gpios.size(); g++) {
//RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"), "gpios");
// check all gpios for EC modules and load into ec_modules_
auto module_params = getEcModuleParam(info_.original_xml, info_.gpios[g].name, "gpio");
ec_module_parameters_.insert(
ec_module_parameters_.end(), module_params.begin(), module_params.end());
for (auto i = 0ul; i < module_params.size(); i++) {
for (auto k = 0ul; k < info_.gpios[g].state_interfaces.size(); k++) {
module_params[i]["state_interface/" +
info_.gpios[g].state_interfaces[k].name] = std::to_string(k);
}
for (auto k = 0ul; k < info_.gpios[g].command_interfaces.size(); k++) {
module_params[i]["command_interface/" +
info_.gpios[g].command_interfaces[k].name] = std::to_string(k);
}
}
}
RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"), "sensors.size:%d",info_.joints.size());
for (uint s = 0; s < info_.sensors.size(); s++) {
RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"), "sensors");
// check all sensors for EC modules and load into ec_modules_
auto module_params = getEcModuleParam(info_.original_xml, info_.sensors[s].name, "sensor");
ec_module_parameters_.insert(
ec_module_parameters_.end(), module_params.begin(), module_params.end());
for (auto i = 0ul; i < module_params.size(); i++) {
for (auto k = 0ul; k < info_.sensors[s].state_interfaces.size(); k++) {
module_params[i]["state_interface/" +
info_.sensors[s].state_interfaces[k].name] = std::to_string(k);
}
for (auto k = 0ul; k < info_.sensors[s].command_interfaces.size(); k++) {
module_params[i]["command_interface/" +
info_.sensors[s].command_interfaces[k].name] = std::to_string(k);
}
}
}
//
master = ecrt_request_master(0);
if (!master) {
perror("ecrt_request_master");
//g_started.store(false); return false;
}
domain1 = ecrt_master_create_domain(master);
if (!domain1){
perror("ecrt_master_create_domain");
//g_started.store(false); return false;
}
for (int i = 0; i < NUM_SLAVES; i++) {
std::cout << "Configuring slave " << i << "..." << std::endl;
{
sc[i] = ecrt_master_slave_config(master, 0, i+1, ZER_VID_PID);
if (!sc[i])
{
std::cout << "Failed slave cfg at pos " << i << std::endl;
}
if (ecrt_slave_config_pdos(sc[i], EC_END, zer_device_syncs))
{
std::cout << "Failed PDO config " << i << std::endl;
}
}
ecrt_slave_config_dc(sc[i], 0x0300, PERIOD_NS, 200, 0, 0);
}
ecrt_domain_reg_pdo_entry_list(domain1,zer_domain1_regs);
if (ecrt_master_activate(master)) {
perror("ecrt_master_activate");
return CallbackReturn::ERROR;
}
domain1_pd = ecrt_domain_data(domain1);
if (!domain1_pd)
{
fprintf(stderr,"domain1_pd null\n");
return CallbackReturn::ERROR;
}
RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"), "Got %li modules", info_.joints.size());
return CallbackReturn::SUCCESS;
}
CallbackReturn EthercatDriver::on_configure(
const rclcpp_lifecycle::State & /*previous_state*/)
{
return CallbackReturn::SUCCESS;
}
std::vector<hardware_interface::StateInterface>
EthercatDriver::export_state_interfaces()
{
RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"), "#######export_state_interfaces:%d,%d,%d",info_.joints.size(),info_.sensors.size(),info_.gpios.size());
std::vector<hardware_interface::StateInterface> state_interfaces;
// export joint state interface
for (uint j = 0; j < info_.joints.size(); j++) {
for (uint i = 0; i < info_.joints[j].state_interfaces.size(); i++) {
RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"),"export:%s,%s\n",info_.joints[j].name.c_str(),info_.joints[j].state_interfaces[i].name.c_str());
state_interfaces.emplace_back(
hardware_interface::StateInterface(
info_.joints[j].name,
info_.joints[j].state_interfaces[i].name,
&hw_joint_states_[j][i]));
}
}
// export sensor state interface
for (uint s = 0; s < info_.sensors.size(); s++) {
for (uint i = 0; i < info_.sensors[s].state_interfaces.size(); i++) {
state_interfaces.emplace_back(
hardware_interface::StateInterface(
info_.sensors[s].name,
info_.sensors[s].state_interfaces[i].name,
&hw_sensor_states_[s][i]));
}
}
// export gpio state interface
for (uint g = 0; g < info_.gpios.size(); g++) {
for (uint i = 0; i < info_.gpios[g].state_interfaces.size(); i++) {
state_interfaces.emplace_back(
hardware_interface::StateInterface(
info_.gpios[g].name,
info_.gpios[g].state_interfaces[i].name,
&hw_gpio_states_[g][i]));
}
}
return state_interfaces;
}
std::vector<hardware_interface::CommandInterface>
EthercatDriver::export_command_interfaces()
{
RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"), "#######export_command_interfaces:%d",info_.joints.size());
std::vector<hardware_interface::CommandInterface> command_interfaces;
// export joint command interface
///std::vector<double> test;
#if 0
for (uint i = 0; i < info_.joints.size(); i++) {
command_interfaces.emplace_back(
hardware_interface::CommandInterface(
info_.joints[i].name,
hardware_interface::HW_IF_POSITION,
&hw_cmd_position_[i]));
}
#endif
for (uint j = 0; j < info_.joints.size(); j++) {
////RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"),"size:%d\n",info_.joints[j].command_interfaces.size());
for (uint i = 0; i < info_.joints[j].command_interfaces.size(); i++) {
RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"),"joints:%s,%s\n",info_.joints[j].name.c_str(),info_.joints[j].command_interfaces[i].name.c_str());
command_interfaces.emplace_back(
hardware_interface::CommandInterface(
info_.joints[j].name,
info_.joints[j].command_interfaces[i].name,
&hw_joint_commands_[j][i]));
}
}
// export sensor command interface
for (uint s = 0; s < info_.sensors.size(); s++) {
for (uint i = 0; i < info_.sensors[s].command_interfaces.size(); i++) {
command_interfaces.emplace_back(
hardware_interface::CommandInterface(
info_.sensors[s].name,
info_.sensors[s].command_interfaces[i].name,
&hw_sensor_commands_[s][i]));
}
}
// export gpio command interface
for (uint g = 0; g < info_.gpios.size(); g++) {
for (uint i = 0; i < info_.gpios[g].command_interfaces.size(); i++) {
printf("gpios:%s,%s\n",info_.gpios[g].name,info_.gpios[g].command_interfaces[i].name);
command_interfaces.emplace_back(
hardware_interface::CommandInterface(
info_.gpios[g].name,
info_.gpios[g].command_interfaces[i].name,
&hw_gpio_commands_[g][i]));
}
}
return command_interfaces;
}
CallbackReturn EthercatDriver::on_activate(
const rclcpp_lifecycle::State & /*previous_state*/)
{
const std::lock_guard<std::mutex> lock(ec_mutex_);
if (activated_) {
RCLCPP_FATAL(rclcpp::get_logger("EthercatDriver"), "Double on_activate()");
return CallbackReturn::ERROR;
}
RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"), "on activate ...please wait...");
if (info_.hardware_parameters.find("control_frequency") == info_.hardware_parameters.end()) {
control_frequency_ = 100;
} else {
control_frequency_ = std::stod(info_.hardware_parameters["control_frequency"]);
}
control_frequency_=1000;
RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"),"control_frequency_:%d\n",control_frequency_);
if (control_frequency_ < 0) {
RCLCPP_FATAL(
rclcpp::get_logger("EthercatDriver"), "Invalid control frequency!");
return CallbackReturn::ERROR;
}
clock_gettime(CLOCK_TO_USE, &wakeupTime);
for(int i=0;i<NUM_SLAVES;i++){
RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"),"slave:%d,ctl offset:%p,sta offset:%p\n",i,zer_offsets[i].ctrl_word,zer_offsets[i].status_word);
}
RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"),"activate finish");
activated_ = true;
return CallbackReturn::SUCCESS;
}
CallbackReturn EthercatDriver::on_deactivate(
const rclcpp_lifecycle::State & /*previous_state*/)
{
const std::lock_guard<std::mutex> lock(ec_mutex_);
RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"), "SSSSSSSSSSSSSSSSSSSSSSSS...");
for(int i=0;i<NUM_SLAVES;i++){
EC_WRITE_U16(domain1_pd + zer_offsets[i].ctrl_word, 0x0007);
// CSV 安全:速度清零
EC_WRITE_S32(domain1_pd + zer_offsets[i].target_velocity, 0);
// CSP 安全:目标位置=当前位置 -> 立即“冻结”
int32_t pv_hold = EC_READ_S32(domain1_pd + zer_offsets[i].position_actual_value);
EC_WRITE_S32(domain1_pd + zer_offsets[i].target_position, pv_hold);
// 可选:扭矩模式也清零(最好维持急停状态,待修改)
EC_WRITE_S16(domain1_pd + zer_offsets[i].target_torque, 0);
EC_WRITE_U16(domain1_pd + zer_offsets[i].ctrl_word, 0x0007);//切换 switched on 状态
}
activated_ = false;
RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"), "System successfully stopped!");
return CallbackReturn::SUCCESS;
}
hardware_interface::return_type EthercatDriver::read(
const rclcpp::Time & /*time*/,
const rclcpp::Duration & /*period*/)
{
// try to lock so we can avoid blocking the read/write loop on the lock.
const std::unique_lock<std::mutex> lock(ec_mutex_, std::try_to_lock);
//printf("read data...\n");
if (lock.owns_lock() && activated_) {
////master_.readData();
readData();
}
return hardware_interface::return_type::OK;
}
void EthercatDriver::check_domain1_state(void)
{
ec_domain_state_t ds;
ecrt_domain_state(domain1, &ds);
if (ds.working_counter != domain1_state.working_counter)
//printf("Domain1: WC %u.\n", ds.working_counter); //当数字变化,表示丢包
if (ds.wc_state != domain1_state.wc_state)
//printf("Domain1: State %u.\n", ds.wc_state);
domain1_state = ds;
}
void EthercatDriver::check_master_state(void)
{
ec_master_state_t ms;
ecrt_master_state(master, &ms);
if (ms.slaves_responding != master_state.slaves_responding)
//printf("%u slave(s).\n", ms.slaves_responding);
if (ms.al_states != master_state.al_states)
//printf("AL states: 0x%02X.\n", ms.al_states);
if (ms.link_up != master_state.link_up)
//printf("Link is %s.\n", ms.link_up ? "up" : "down");
master_state = ms;
}
void EthercatDriver::check_slave_config_states(void)
{
ec_slave_config_state_t s;
for (int i = 0; i < NUM_SLAVES; ++i) {
if (!sc[i]) continue;
ecrt_slave_config_state(sc[i], &s);
if (s.al_state != sc_state[i].al_state)
//printf("[S%02d] State 0x%02X.\n", i, s.al_state);
if (s.online != sc_state[i].online)
//printf("[S%02d] %s.\n", i, s.online ? "online" : "offline");
if (s.operational != sc_state[i].operational)
//printf("[S%02d] %soperational.\n", i, s.operational ? "" : "Not ");
sc_state[i] = s;
}
}
struct timespec EthercatDriver::timespec_add(struct timespec time1, struct timespec time2)
{
struct timespec result;
if ((time1.tv_nsec + time2.tv_nsec) >= NSEC_PER_SEC) {
result.tv_sec = time1.tv_sec + time2.tv_sec + 1;
result.tv_nsec = time1.tv_nsec + time2.tv_nsec - NSEC_PER_SEC;
} else {
result.tv_sec = time1.tv_sec + time2.tv_sec;
result.tv_nsec = time1.tv_nsec + time2.tv_nsec;
}
return result;
}
void EthercatDriver::readData(){
#if 0
static int print_cnt=0;
if(print_cnt++%1000==0){
for(int i=0;i<info_.joints.size();i++){
printf("[%d]%s:",i,info_.joints[i].name.c_str());
for(int j=0;j<info_.joints[i].command_interfaces.size();j++)
printf("%.2f,",hw_joint_commands_[i][j]);
printf("\n");
}
printf("\n");
}
#endif
wakeupTime = timespec_add(wakeupTime, cycletime);
clock_nanosleep(CLOCK_TO_USE, TIMER_ABSTIME, &wakeupTime, NULL); //使用绝对时间,精确等待下一个周期,避免循环执行的时间造成周期漂移
ecrt_master_application_time(master, TIMESPEC2NS(wakeupTime)); // 更新主站应用时间
ecrt_master_receive(master); //接收 EtherCAT 帧
ecrt_domain_process(domain1); //处理域数据
// check process data state (optional)
check_domain1_state(); //检查域状态
if (counter) {
counter--;
} else { // do this at 1 Hz
counter = FREQUENCY;
// check for master state (optional)
//check_master_state(); //检查主站状态
// check for slave configuration state(s)
//check_slave_config_states(); //检查从站状态
}
if (!inited) {
for (int i = 0; i < NUM_SLAVES; ++i) {
command[i] = 0x004F;
status[i] = 0x000F;
last_status[i] = status[i];
EC_WRITE_U16(domain1_pd + zer_offsets[i].ctrl_word, 0x0080);
}
inited = 1;
}
for (int i = 0; i < NUM_SLAVES; ++i)
{
uint16_t sw = EC_READ_U16(domain1_pd + zer_offsets[i].status_word);
int32_t pos = EC_READ_S32(domain1_pd + zer_offsets[i].position_actual_value);
int32_t vel = EC_READ_S32(domain1_pd + zer_offsets[i].velocity_actual_value);
int16_t tv = EC_READ_S16(domain1_pd + zer_offsets[i].torque_actual_value);
int8_t md = EC_READ_S8 (domain1_pd + zer_offsets[i].modes_of_operation_display);
uint16_t err = EC_READ_U16(domain1_pd + zer_offsets[i].error_code);
hw_joint_states_[i][0]=pos*count_to_rad;
hw_joint_states_[i][1]=vel;
hw_joint_states_[i][2]=40960;//err
//hw_joint_states_[i][2]=err;
///RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"), "[%d],state:%.1f,%.1f,0x%04x",i,hw_joint_states_[i][0],hw_joint_states_[i][1],err);
status[i] = sw; //侦测子站状态字变化
if (status[i] != last_status[i]) {
last_status[i] = status[i];
}
if ((status[i]&0x006f)==0x0008){
uint16_t err = EC_READ_U16(domain1_pd + zer_offsets[i].error_code);
double fault=hw_joint_commands_[i][0];
if(fault==1.0&&err>0){
RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"), "[%d],fault code 0x%04x",i,err);
EC_WRITE_U16(domain1_pd + zer_offsets[i].ctrl_word, 0x0080);
}
}
///static double last_enable[12];
double enable=hw_joint_commands_[i][1];
if(enable!=1){
EC_WRITE_S32(domain1_pd + zer_offsets[i].target_velocity, 0);
EC_WRITE_U16(domain1_pd + zer_offsets[i].ctrl_word, 0x0007);
continue;
}
//write
if ((status[i] & command[i]) == 0x0001 || (status[i] & command[i]) == 0x0040) {
EC_WRITE_U16(domain1_pd + zer_offsets[i].ctrl_word, 0x0006); // Ready to switch on
command[i] = 0x006F;
///printf("SS:%d\n",i);
} else if ((status[i] & command[i]) == 0x0021) {
EC_WRITE_U16(domain1_pd + zer_offsets[i].ctrl_word, 0x0007); // Switched on
command[i] = 0x006F;
// 同步当前位置为目标位置
int32_t pv2 = EC_READ_S32(domain1_pd + zer_offsets[i].position_actual_value);
EC_WRITE_S32(domain1_pd + zer_offsets[i].target_position, pv2);
/////g_cmd.target_pos[i].store(pv2, std::memory_order_relaxed);
//printf("TTTTTTTTT:%d\n",i);
} else if ((status[i] & command[i]) == 0x0023) {
EC_WRITE_U16(domain1_pd + zer_offsets[i].ctrl_word, 0x000f); // Operation enabled
command[i] = 0x006F;
//printf("VVVVVVVV:%d\n",i);
} else if ((status[i] & command[i]) == 0x0027) {
// ---- 4) 运行态写目标(按模式/运行意图) ----
//if(enable==1)
if(true)
{
//EC_WRITE_U16(domain1_pd + offsets[i].ctrl_word, 0x001F); // 仅在PP模式下需要
switch (mode_cmd) {
case CYCLIC_VELOCITY: // 9
////EC_WRITE_S32(domain1_pd + zer_offsets[i].target_velocity, vel_cmd);
break;
case CYCLIC_POSITION: { // 8
// ---- 绝对式目标生成,不再用 pv 做增量基准 ----
////printf("%02d-->%d,%d,%d\n",i,hw_joint_commands_[i][0],hw_joint_commands_[i][1],hw_joint_commands_[i][2]);
///break;
// 每轴记住“上次下发的命令位置”
static int32_t csp_cmd_pos[NUM_SLAVES]; // 上次下发到 0x607A 的值
static uint8_t csp_inited[NUM_SLAVES] = {0}; // 是否已初始化
// 定点限速累计器:解决 v_max < FREQUENCY 时整除为 0 的问题
static int64_t vmax_acc[NUM_SLAVES] = {0}; // 单位counts/s 累加器
// 进入/首次运行:把命令位置对齐到“当前实际位置”,避免冲击
if (!csp_inited[i] || last_mode_cmd[i] != CYCLIC_POSITION) {
csp_cmd_pos[i] = pos; // 先对齐
vmax_acc[i] = 0;
csp_inited[i] = 1;
}
double target_pos=hw_joint_commands_[i][2]*rad_to_count;
static double last_pos[12];
if(target_pos!=last_pos[i]){
last_pos[i]=target_pos;
}
// 期望的“最终绝对目标”
const int32_t goal = target_pos;///g_cmd.target_pos[i].load(std::memory_order_relaxed);
///if(i==0)
///printf("%d,goal:%d,pv:%d,flag:%d\n",i,goal,pv,goal_flag[i]);
static int pos_cnt=0;
//if(pos_cnt++%500==0)
// RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"), "[%d],cur:%d,dst:%.1f",i,pos,target_pos);
// 本周期允许的最大步长counts/周期),用定点累加保证平均速度上限严格等于 CSP_MAX_VEL_COUNTS_PER_S
vmax_acc[i] += (int64_t)CSP_MAX_VEL_COUNTS_PER_S; // +v [counts/s]
int32_t max_step = (int32_t)(vmax_acc[i] / FREQUENCY); // 下取整,得到本周期可用步长
vmax_acc[i] -= (int64_t)max_step * FREQUENCY; // 保留余数到下周期
// 相对“命令轨迹”的误差(不是相对 pv
const int32_t err_cmd = goal - csp_cmd_pos[i];
// 死区:足够近就贴合到 goal
if (err_cmd > -CSP_POS_DEADBAND && err_cmd < CSP_POS_DEADBAND) {
csp_cmd_pos[i] = goal;
} else {
// 限速迈步:命令轨迹只按时间往 goal 逼近
if (err_cmd > 0) csp_cmd_pos[i] += (err_cmd > max_step) ? max_step : err_cmd;
else csp_cmd_pos[i] += (err_cmd < -max_step) ? -max_step : err_cmd;
}
// 可选:跟随误差防护(避免 following error 过大时继续“加命令”)
// const int32_t follow_err = csp_cmd_pos[i] - pv;
// const int32_t FE_LIMIT = (int32_t) (你的驱动跟随误差阈值); // 参考驱动参数
// if (std::abs(follow_err) > FE_LIMIT) {
// // 例如:冻结或减半 max_step给驱动时间追上
// }
#if 1
// 下发“绝对目标”(不是增量)
EC_WRITE_S32(domain1_pd + zer_offsets[i].target_position, csp_cmd_pos[i]);
// 回显给上层(/joint_states_cmd 用)
//////g_state.pos_cmd[i].store(csp_cmd_pos[i], std::memory_order_relaxed);
#endif
break;
}
case CYCLIC_TORQUE: // 10
///////EC_WRITE_S16(domain1_pd + zer_offsets[i].target_torque, tor_cmd);
break;
default:
// 未知模式:默认速度模式安全置 0
//EC_WRITE_S32(domain1_pd + offsets[i].target_velocity, 0);
break;
}
} else {
// run_enable=false清零目标
EC_WRITE_U16(domain1_pd + zer_offsets[i].ctrl_word, 0x0007);
// CSV 安全:速度清零
EC_WRITE_S32(domain1_pd + zer_offsets[i].target_velocity, 0);
// CSP 安全:目标位置=当前位置 -> 立即“冻结”
int32_t pv_hold = EC_READ_S32(domain1_pd + zer_offsets[i].position_actual_value);
EC_WRITE_S32(domain1_pd + zer_offsets[i].target_position, pv_hold);
// 可选:扭矩模式也清零(最好维持急停状态,待修改)
EC_WRITE_S16(domain1_pd + zer_offsets[i].target_torque, 0);
EC_WRITE_U16(domain1_pd + zer_offsets[i].ctrl_word, 0x0007);//切换 switched on 状态
}
}
}
//Final
if (sync_ref_counter) {
sync_ref_counter--;
} else {
sync_ref_counter = 1; // sync every 2 cycle
clock_gettime(CLOCK_TO_USE, &time);
ecrt_master_sync_reference_clock_to(master, TIMESPEC2NS(time)); //每隔一个周期同步时钟
}
ecrt_master_sync_slave_clocks(master);
// send process data
ecrt_domain_queue(domain1); //排队域数据
ecrt_master_send(master); //发送ETHERCAT帧
}
void EthercatDriver::writeData(){
///return;
for (int i = 0; i < NUM_SLAVES; ++i){
int8_t mode_cmd=8;
int32_t pv = EC_READ_S32(domain1_pd + zer_offsets[i].position_actual_value);
uint16_t sw = EC_READ_U16(domain1_pd + zer_offsets[i].status_word);
status[i] = sw; //侦测子站状态字变化
if (status[i] != last_status[i]) {
// printf("[S%02d] 状态改变为: 0x%04X\n", i, status[i]);
last_status[i] = status[i];
}
///printf("%d error status:%04x,%04x\n",i,status[i],status[i]&0x006f);
#if 0
static double last_reset[12];
if(last_reset[i]!=reset){
RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"), "[%d],reset:%.1f",i,reset);
last_reset[i]=reset;
}
#endif
if ((status[i]&0x006f)==0x0008){
static int err_cnt=0;
uint16_t err = EC_READ_U16(domain1_pd + zer_offsets[i].error_code);
if(err_cnt++%500==0)
printf("%d error status:%04x,err:%04x\n",i,status[i],err);
}
if ((status[i] & command[i]) == 0x0001 || (status[i] & command[i]) == 0x0040) {
EC_WRITE_U16(domain1_pd + zer_offsets[i].ctrl_word, 0x0006); // Ready to switch on
command[i] = 0x006F;
///printf("SS:%d\n",i);
} else if ((status[i] & command[i]) == 0x0021) {
EC_WRITE_U16(domain1_pd + zer_offsets[i].ctrl_word, 0x0007); // Switched on
command[i] = 0x006F;
// 同步当前位置为目标位置
int32_t pv2 = EC_READ_S32(domain1_pd + zer_offsets[i].position_actual_value);
EC_WRITE_S32(domain1_pd + zer_offsets[i].target_position, pv2);
/////g_cmd.target_pos[i].store(pv2, std::memory_order_relaxed);
//printf("TTTTTTTTT:%d\n",i);
} else if ((status[i] & command[i]) == 0x0023) {
EC_WRITE_U16(domain1_pd + zer_offsets[i].ctrl_word, 0x000f); // Operation enabled
command[i] = 0x006F;
//printf("VVVVVVVV:%d\n",i);
} else if ((status[i] & command[i]) == 0x0027) {
// ---- 4) 运行态写目标(按模式/运行意图) ----
static double last_enable[12];
double enable=hw_joint_commands_[i][2];
#if 0
if(last_enable[i]!=enable){
//std::cout<<i<<",enable:"<<enable<<std::endl;
RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"), "[%d],enable:%.1f",i,enable);
last_enable[i]=enable;
}
#endif
if(enable==1)
{
//EC_WRITE_U16(domain1_pd + offsets[i].ctrl_word, 0x001F); // 仅在PP模式下需要
switch (mode_cmd) {
case CYCLIC_VELOCITY: // 9
////////EC_WRITE_S32(domain1_pd + zer_offsets[i].target_velocity, vel_cmd);
break;
case CYCLIC_POSITION: { // 8
// ---- 绝对式目标生成,不再用 pv 做增量基准 ----
////printf("%02d-->%d,%d,%d\n",i,hw_joint_commands_[i][0],hw_joint_commands_[i][1],hw_joint_commands_[i][2]);
///break;
// 每轴记住“上次下发的命令位置”
static int32_t csp_cmd_pos[NUM_SLAVES]; // 上次下发到 0x607A 的值
static uint8_t csp_inited[NUM_SLAVES] = {0}; // 是否已初始化
// 定点限速累计器:解决 v_max < FREQUENCY 时整除为 0 的问题
static int64_t vmax_acc[NUM_SLAVES] = {0}; // 单位counts/s 累加器
// 进入/首次运行:把命令位置对齐到“当前实际位置”,避免冲击
if (!csp_inited[i] || last_mode_cmd[i] != CYCLIC_POSITION) {
csp_cmd_pos[i] = pv; // 先对齐
vmax_acc[i] = 0;
csp_inited[i] = 1;
}
double target_pos=hw_joint_commands_[i][0];
static double last_pos[12];
if(target_pos!=last_pos[i]){
last_pos[i]=target_pos;
}
RCLCPP_INFO(rclcpp::get_logger("EthercatDriver"), "[%d],pos:%.1f",i,target_pos);
// 期望的“最终绝对目标”
const int32_t goal = pv;///g_cmd.target_pos[i].load(std::memory_order_relaxed);
///if(i==0)
///printf("%d,goal:%d,pv:%d,flag:%d\n",i,goal,pv,goal_flag[i]);
// 本周期允许的最大步长counts/周期),用定点累加保证平均速度上限严格等于 CSP_MAX_VEL_COUNTS_PER_S
vmax_acc[i] += (int64_t)CSP_MAX_VEL_COUNTS_PER_S; // +v [counts/s]
int32_t max_step = (int32_t)(vmax_acc[i] / FREQUENCY); // 下取整,得到本周期可用步长
vmax_acc[i] -= (int64_t)max_step * FREQUENCY; // 保留余数到下周期
// 相对“命令轨迹”的误差(不是相对 pv
const int32_t err_cmd = goal - csp_cmd_pos[i];
// 死区:足够近就贴合到 goal
if (err_cmd > -CSP_POS_DEADBAND && err_cmd < CSP_POS_DEADBAND) {
csp_cmd_pos[i] = goal;
} else {
// 限速迈步:命令轨迹只按时间往 goal 逼近
if (err_cmd > 0) csp_cmd_pos[i] += (err_cmd > max_step) ? max_step : err_cmd;
else csp_cmd_pos[i] += (err_cmd < -max_step) ? -max_step : err_cmd;
}
// 可选:跟随误差防护(避免 following error 过大时继续“加命令”)
// const int32_t follow_err = csp_cmd_pos[i] - pv;
// const int32_t FE_LIMIT = (int32_t) (你的驱动跟随误差阈值); // 参考驱动参数
// if (std::abs(follow_err) > FE_LIMIT) {
// // 例如:冻结或减半 max_step给驱动时间追上
// }
#if 0
// 下发“绝对目标”(不是增量)
EC_WRITE_S32(domain1_pd + zer_offsets[i].target_position, csp_cmd_pos[i]);
// 回显给上层(/joint_states_cmd 用)
//////g_state.pos_cmd[i].store(csp_cmd_pos[i], std::memory_order_relaxed);
#endif
break;
}
case CYCLIC_TORQUE: // 10
///////EC_WRITE_S16(domain1_pd + zer_offsets[i].target_torque, tor_cmd);
break;
default:
// 未知模式:默认速度模式安全置 0
//EC_WRITE_S32(domain1_pd + offsets[i].target_velocity, 0);
break;
}
} else {
// run_enable=false清零目标
EC_WRITE_U16(domain1_pd + zer_offsets[i].ctrl_word, 0x0007);
// CSV 安全:速度清零
EC_WRITE_S32(domain1_pd + zer_offsets[i].target_velocity, 0);
// CSP 安全:目标位置=当前位置 -> 立即“冻结”
int32_t pv_hold = EC_READ_S32(domain1_pd + zer_offsets[i].position_actual_value);
EC_WRITE_S32(domain1_pd + zer_offsets[i].target_position, pv_hold);
// 可选:扭矩模式也清零(最好维持急停状态,待修改)
EC_WRITE_S16(domain1_pd + zer_offsets[i].target_torque, 0);
EC_WRITE_U16(domain1_pd + zer_offsets[i].ctrl_word, 0x0007);//切换 switched on 状态
}
}
}
if (sync_ref_counter) {
sync_ref_counter--;
} else {
sync_ref_counter = 1; // sync every 2 cycle
clock_gettime(CLOCK_TO_USE, &time);
ecrt_master_sync_reference_clock_to(master, TIMESPEC2NS(time)); //每隔一个周期同步时钟
}
ecrt_master_sync_slave_clocks(master);
// send process data
ecrt_domain_queue(domain1); //排队域数据
ecrt_master_send(master); //发送ETHERCAT帧
}
hardware_interface::return_type EthercatDriver::write(
const rclcpp::Time & /*time*/,
const rclcpp::Duration & /*period*/)
{
// try to lock so we can avoid blocking the read/write loop on the lock.
const std::unique_lock<std::mutex> lock(ec_mutex_, std::try_to_lock);
//printf("write data...\n");
if (lock.owns_lock() && activated_) {
//writeData();
}
return hardware_interface::return_type::OK;
}
std::vector<std::unordered_map<std::string, std::string>> EthercatDriver::getEcModuleParam(
std::string & urdf,
std::string component_name,
std::string component_type)
{
// Check if everything OK with URDF string
if (urdf.empty()) {
throw std::runtime_error("empty URDF passed to robot");
}
tinyxml2::XMLDocument doc;
if (!doc.Parse(urdf.c_str()) && doc.Error()) {
throw std::runtime_error("invalid URDF passed in to robot parser");
}
if (doc.Error()) {
throw std::runtime_error("invalid URDF passed in to robot parser");
}
tinyxml2::XMLElement * robot_it = doc.RootElement();
if (std::string("robot").compare(robot_it->Name())) {
throw std::runtime_error("the robot tag is not root element in URDF");
}
const tinyxml2::XMLElement * ros2_control_it = robot_it->FirstChildElement("ros2_control");
if (!ros2_control_it) {
throw std::runtime_error("no ros2_control tag");
}
std::vector<std::unordered_map<std::string, std::string>> module_params;
std::unordered_map<std::string, std::string> module_param;
while (ros2_control_it) {
const auto * ros2_control_child_it = ros2_control_it->FirstChildElement(component_type.c_str());
while (ros2_control_child_it) {
if (!component_name.compare(ros2_control_child_it->Attribute("name"))) {
const auto * ec_module_it = ros2_control_child_it->FirstChildElement("ec_module");
while (ec_module_it) {
module_param.clear();
module_param["name"] = ec_module_it->Attribute("name");
const auto * plugin_it = ec_module_it->FirstChildElement("plugin");
if (NULL != plugin_it) {
module_param["plugin"] = plugin_it->GetText();
}
const auto * param_it = ec_module_it->FirstChildElement("param");
while (param_it) {
module_param[param_it->Attribute("name")] = param_it->GetText();
param_it = param_it->NextSiblingElement("param");
}
module_params.push_back(module_param);
ec_module_it = ec_module_it->NextSiblingElement("ec_module");
}
}
ros2_control_child_it = ros2_control_child_it->NextSiblingElement(component_type.c_str());
}
ros2_control_it = ros2_control_it->NextSiblingElement("ros2_control");
}
return module_params;
}
} // namespace ethercat_driver
#include "pluginlib/class_list_macros.hpp"
PLUGINLIB_EXPORT_CLASS(
ethercat_driver::EthercatDriver, hardware_interface::SystemInterface)

304
ecrt_dev/src/test_motor.cpp Normal file
View File

@@ -0,0 +1,304 @@
#include "rclcpp/rclcpp.hpp"
#include <filesystem>
#include <fstream> // 添加这行来支持文件流操作
#include <time.h>
#include "std_msgs/msg/string.hpp"
#include "trajectory_msgs/msg/joint_trajectory.hpp"
#include "trajectory_msgs/msg/joint_trajectory_point.hpp"
#include "control_msgs/msg/dynamic_interface_group_values.hpp"
#include "control_msgs/action/follow_joint_trajectory.hpp"
#include "sensor_msgs/msg/joint_state.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
namespace fs = std::filesystem;
constexpr double rad_to_count= 524288.0/(2*M_PI);
constexpr double count_to_rad=2*M_PI/524288.0;
class TestMotor : public rclcpp::Node
{
public:
TestMotor();
~TestMotor();
int motor_dst(std::string name,double dst);//add by hehe
//void ctrl_motor(int id,double pos,int reset,int enable);
void motor_fault(int id,double fault);
void motor_enable(int id,double enable);
void motor_pos(int id,double pos);
void motor_loop(int motor_id,int cnt);
void motor_action(int id,double angle);
void all_motor();
void ControlLoop();
private:
rclcpp::Publisher<trajectory_msgs::msg::JointTrajectory>::SharedPtr cmdPub_;
rclcpp::Publisher<control_msgs::msg::DynamicInterfaceGroupValues>::SharedPtr gpioPub_;
rclcpp::Subscription<sensor_msgs::msg::JointState>::SharedPtr jointStatesSub_;
rclcpp_action::Client<control_msgs::action::FollowJointTrajectory>::SharedPtr client_;
// 文件流相关
std::ofstream data_file_; // 用于写入数据的文件流
std::string data_file_path_; // 数据文件路径
rclcpp::TimerBase::SharedPtr controlTimer_;
rclcpp::Time lastTime_; // 移至类成员
bool isRunning_;
bool isPaused_;
bool isFinished_;
bool isRobotEnabled_;
bool enableCommandExecuted_;
int loop_cnt=0;
int jogDirection_;
//add by hehe
std::unordered_map<std::string,double> curMap_;
std::unordered_map<std::string,double> dstMap_;
//control_msgs::msg::DynamicInterfaceGroupValues posMsg_;
sensor_msgs::msg::JointState curJointSta_;
//add by hehe end
// 执行当前状态对应的动作
void JointStatesCallback(const sensor_msgs::msg::JointState::SharedPtr msg);
};
// 机器人控制器构造函数
TestMotor::TestMotor() : Node("test_motor_node")
{
std::cout << "TestMotor Node is created!" << std::endl;
// 创建发布者
cmdPub_ = this->create_publisher<trajectory_msgs::msg::JointTrajectory>("/trajectory_controller/joint_trajectory", 10);
//action
client_=rclcpp_action::create_client<control_msgs::action::FollowJointTrajectory>(this,"/trajectory_controller/follow_joint_trajectory");
// 创建发布者
gpioPub_ = this->create_publisher<control_msgs::msg::DynamicInterfaceGroupValues>("/gpio_command_controller/commands", 10);
// 订阅仿真状态
jointStatesSub_ = this->create_subscription<sensor_msgs::msg::JointState>("/joint_states", 10,std::bind(&TestMotor::JointStatesCallback, this, std::placeholders::_1));
lastTime_ = this->now(); // 初始化时间
// 创建定时器每10ms执行一次控制逻辑频率100Hz
controlTimer_ = this->create_wall_timer(std::chrono::milliseconds(5000),std::bind(&TestMotor::ControlLoop, this)); // 绑定到新的控制函数);
curMap_.clear();
dstMap_.clear();
//posMsg_.interface_groups.resize(12);
//posMsg_.interface_values.resize(12);
motor_enable(6,1);
motor_enable(13,1);
std::cout << "TestMotor Node is created finished!" << std::endl;
}
TestMotor::~TestMotor()
{
std::cout << "Robot controller stopped." << std::endl;
}
void TestMotor::JointStatesCallback(const sensor_msgs::msg::JointState::SharedPtr msg)
{
if (!msg) { // 检查消息是否有效
std::cout << "get null joint states!" << std::endl;
return;
}
for(int i=0;i<msg->name.size();i++)
{
curMap_[msg->name[i]] = msg->position[i];
///std::cout<<"cur:"<<msg->name[i]<<":"<<curMap_[msg->name[i]]<<std::endl;
}
}
int TestMotor::motor_dst(std::string name,double dst){
control_msgs::msg::DynamicInterfaceGroupValues posMsg_;
double val=curMap_[name];
double diff=dst-val;
double tempJointValue=0;
float delta=0.0;
if(diff>600){
delta=120.0;
tempJointValue=val+delta;
}else if(diff<-600){
delta=-120.0;
tempJointValue=val+delta;
}else{
return 0;
}
posMsg_.interface_groups.push_back(name);
control_msgs::msg::InterfaceValue tempValue;
tempValue.interface_names = {"position"};
tempValue.values = {tempJointValue};
posMsg_.interface_values.push_back(tempValue);
return 0;
}
// 状态机主循环
void TestMotor::ControlLoop() {
//Gpio_publish_joint_trajectory();
all_motor();
}
void TestMotor::motor_pos(int id,double delta)
{
control_msgs::msg::DynamicInterfaceGroupValues posMsg_;
std::string target_motor="joint_" + std::to_string(id);
for(int i=0;i<12;i++){
std::string tempInterfaceGroup = "joint_" + std::to_string(i+1);
posMsg_.interface_groups.push_back(tempInterfaceGroup);
control_msgs::msg::InterfaceValue tempValue;
//position
tempValue.interface_names = {"position"};
if(id==0){
tempValue.values = {curMap_[tempInterfaceGroup]+delta};
}else{
if(tempInterfaceGroup!=target_motor)
tempValue.values = {curMap_[tempInterfaceGroup]};
else
tempValue.values = {curMap_[tempInterfaceGroup]+delta};
}
std::cout<<tempInterfaceGroup<<":"<<curMap_[tempInterfaceGroup]<<"-->"<<tempValue.values[0]<<std::endl;
posMsg_.interface_values.push_back(tempValue);
}
gpioPub_->publish(posMsg_);
usleep(10000);
}
void TestMotor::motor_fault(int id,double fault)
{
control_msgs::msg::DynamicInterfaceGroupValues posMsg_;
std::string target_motor="joint_" + std::to_string(id);
std::cout<<"fault:";
for(int i=0;i<12;i++){
std::string tempInterfaceGroup = "joint_" + std::to_string(i+1);
posMsg_.interface_groups.push_back(tempInterfaceGroup);
control_msgs::msg::InterfaceValue tempValue;
//reset
tempValue.interface_names = {"fault"};
if(id==0){
tempValue.values = {fault};
}else{
if(tempInterfaceGroup!=target_motor)
tempValue.values = {0};
else
tempValue.values = {fault};
}
std::cout<<tempInterfaceGroup<<":"<<tempValue.values[0]<<",";
posMsg_.interface_values.push_back(tempValue);
}
std::cout<<std::endl;
gpioPub_->publish(posMsg_);
usleep(50000);
}
void TestMotor::motor_enable(int id,double enable)
{
control_msgs::msg::DynamicInterfaceGroupValues posMsg_;
std::string target_motor="joint_" + std::to_string(id);
std::cout<<"enable:";
for(int i=0;i<12;i++){
std::string tempInterfaceGroup = "joint_" + std::to_string(i+1);
posMsg_.interface_groups.push_back(tempInterfaceGroup);
control_msgs::msg::InterfaceValue tempValue;
//enable
tempValue.interface_names = {"enable"};
#if 1
if(i<id)
tempValue.values = {enable};
else
tempValue.values = {0};
#else
if(id==0){
tempValue.values = {enable};
}else{
if(tempInterfaceGroup!=target_motor)
tempValue.values = {0};
else
tempValue.values = {enable};
}
#endif
std::cout<<tempInterfaceGroup<<":"<<tempValue.values[0]<<",";
posMsg_.interface_values.push_back(tempValue);
}
std::cout<<std::endl;
gpioPub_->publish(posMsg_);
usleep(1000000);
}
void TestMotor::motor_action(int id,double delta)
{
//std::cout<<"motor_action:"<<id<<","<<delta<<std::endl;
if(client_->wait_for_action_server(std::chrono::seconds(5))){
auto goal=control_msgs::action::FollowJointTrajectory::Goal();
goal.trajectory.joint_names={"joint_1","joint_2","joint_3","joint_4","joint_5","joint_6","joint_7","joint_8","joint_9","joint_10","joint_11","joint_12"};
trajectory_msgs::msg::JointTrajectoryPoint point;
for(int i=0;i<12;i++){
std::string joint="joint_"+std::to_string(i+1);
//if(id==(i+1))
if(i<12)
point.positions.push_back(curMap_[joint]+delta);
else
point.positions.push_back(curMap_[joint]);
printf("%s: %.2f/%.2f\n",joint.c_str(),curMap_[joint],point.positions[i]);
}
goal.trajectory.points.push_back(point);
auto send_goal_option=rclcpp_action::Client<control_msgs::action::FollowJointTrajectory>::SendGoalOptions();
send_goal_option.goal_response_callback=[this](auto res){
auto goal_handle=res.get();
if(goal_handle){
printf("goal has be accept!!!\n");
}
};
send_goal_option.feedback_callback=[this](auto,auto feedback){
for(int i=0;i<12;i++){
printf("%s,%.3f/%.3f\n",feedback->joint_names[i].c_str(),feedback->desired.positions[i],feedback->actual.positions[i]);
}
};
send_goal_option.result_callback=[this](auto ret){
if(ret.code==rclcpp_action::ResultCode::SUCCEEDED)
printf("action ret succeed\n");
else if(ret.code==rclcpp_action::ResultCode::ABORTED)
printf("action ret aborted\n");
};
client_->async_send_goal(goal,send_goal_option);
}else{
printf("wait action server error\n");
}
}
void TestMotor::motor_loop(int motor_id,int loop_cnt){
std::cout<<"start test:"<<motor_id<<std::endl;
motor_fault(0,1);
//motor_enable(motor_id,1);
if(loop_cnt%2==0)
//motor_pos(motor_id,3000);
motor_action(motor_id,-0.05);
else
//motor_pos(motor_id,-3000);
motor_action(motor_id,0.05);
///usleep(3*1000000);
}
void TestMotor::all_motor(){
loop_cnt+=1;
static int sw=0;
motor_loop(0,loop_cnt);
#if 0
int mid=loop_cnt%100;
if(mid<13&&mid>0)
motor_loop(mid,sw);
else{
loop_cnt=0;
sw+=1;
motor_loop(1,sw);
}
#endif
#if 0
motor_loop(2,loop_cnt);
motor_loop(3,loop_cnt);
motor_loop(4,loop_cnt);
motor_loop(5,loop_cnt);
motor_loop(6,loop_cnt);
motor_loop(7,loop_cnt);
motor_loop(8,loop_cnt);
motor_loop(9,loop_cnt);
motor_loop(10,loop_cnt);
motor_loop(11,loop_cnt);
motor_loop(12,loop_cnt);
#endif
}
int main(int argc,char* argv[]){
rclcpp::init(argc,argv);
auto node=std::make_shared<TestMotor>();
///usleep(10000000);
///node->all_motor();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}

28
ethercat_dev/readme.txt Normal file
View File

@@ -0,0 +1,28 @@
#程序需要开发板安装RT Linux系统
#新建mt_ws文件夹将src文件放在mt_ws下
#加载环境
sudo ethercatctl start
source /opt/ros/humble/setup.bash
#构建(在工作空间下)
colcon build --symlink-install
给可执行文件setcap
cmake --build build/ethercat_control --target setcap_ethercat_node
# 加载工作空间环境
source ~/mt_ws/install/setup.bash
#运行主程序
ros2 run ethercat_control ethercat_node
#新开终端运行测试指令
ros2 topic pub --rate 10 /ethercat/set std_msgs/msg/String "data: '0 pos 0; 1 pos 0; 2 pos 0;3 pos 0'"
第一个参数为电机Ethercat上的id
第二个参数为模式 分为pos vel tor对应位置、速度、扭矩模式
第三个参数为位置/速度/扭矩设定值
举例: 0 pos 0 为将0号电机运行至0位
1 pos 65535 为将1号电机运行至180度位置

View File

@@ -0,0 +1,70 @@
cmake_minimum_required(VERSION 3.8)
project(ethercat_control)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(sensor_msgs REQUIRED)
# IGH ecrt如需要设置自定义路径
find_library(ECRT_LIB
NAMES ethercat ecrt
PATHS /opt/etherlab/lib /usr/local/lib /usr/lib
)
if(NOT ECRT_LIB)
message(FATAL_ERROR "ecrt not found; set CMAKE_PREFIX_PATH or system ld path.")
endif()
include_directories(include)
# 核心对象文件
add_library(ethercat_core_obj OBJECT
src/motor_control.cpp
src/ethercat_core.cpp
)
# 静态/共享库(任选其一)
add_library(ethercat_core STATIC $<TARGET_OBJECTS:ethercat_core_obj>)
target_link_libraries(ethercat_core ${ECRT_LIB} pthread)
# 节点
add_executable(ethercat_node src/ethercat_node.cpp)
target_link_libraries(ethercat_node ethercat_core)
ament_target_dependencies(ethercat_node rclcpp std_msgs sensor_msgs rcl_interfaces)
# add_executable(ethercat_csp_test src/ethercat_csp_test.cpp)
# ament_target_dependencies(ethercat_csp_test rclcpp std_msgs)
# install(TARGETS ethercat_csp_test DESTINATION lib/${PROJECT_NAME})
install(TARGETS ethercat_node
DESTINATION lib/${PROJECT_NAME})
install(DIRECTORY include/
DESTINATION include)
install(DIRECTORY launch
DESTINATION share/${PROJECT_NAME}/
)
# 测试程序
# add_executable(ethercat_topic_test src/ethercat_topic_test.cpp)
# ament_target_dependencies(ethercat_topic_test rclcpp std_msgs)
# install(TARGETS ethercat_topic_test
# DESTINATION lib/${PROJECT_NAME})
# 创建一个手动触发的目标:为真实二进制设置 cap_sys_nice
add_custom_target(setcap_ethercat_node
COMMAND ${CMAKE_COMMAND} -E echo "Setting cap_sys_nice on:"
COMMAND ${CMAKE_COMMAND} -E echo "$<TARGET_FILE:ethercat_node>"
COMMAND sudo setcap cap_sys_nice+ep $<TARGET_FILE:ethercat_node>
COMMAND getcap $<TARGET_FILE:ethercat_node>
DEPENDS ethercat_node
VERBATIM
)
ament_package()

View File

@@ -0,0 +1,5 @@
#pragma once
#include <thread>
void start_rt_cyclic_thread(); // 启动实时线程跑 cyclic_task()
void cli_command_loop(); // 在主线程调用:阻塞读命令

View File

@@ -0,0 +1,65 @@
#pragma once
#include "ecrt.h"
#include "ethercat_control/motor_control.hpp" // 电机控制库
#define ZER_Device0 0,0 /*EtherCAT address on the bus 别名,位置*/
#define VID_PID 0x00202288,0x00000384 /*Vendor ID, product code 厂商ID和产品代码*/
typedef struct {
unsigned int ctrl_word; // 0x6040:00 1
unsigned int target_position; // 0x607A:00 1
unsigned int target_velocity; // 0x60FF:00 1
unsigned int operation_mode; // 0x6060:00 1
unsigned int status_word; // 0x6041:00 1
unsigned int position_actual_value; // 0x6064:00 1
unsigned int velocity_actual_value; // 0x606C:00 1
unsigned int error_code; // 0x603F:00 1
unsigned int modes_of_operation_display; // 0x6061:00 1
} zer_offset_t;
static zer_offset_t zer_offsets[NUM_SLAVES];
// ------------------- PDO 定义CSV/CSP/CST对应 0x1600/0x1A00 -------------------
// 下列结构由IGH主站 cstruct 自动生成
const static ec_pdo_entry_reg_t zer_domain1_regs[] = {
{ZER_Device0, VID_PID, 0x6040, 0, &zer_offsets[0].ctrl_word}, // 控制字
{ZER_Device0, VID_PID, 0x607A, 0, &zer_offsets[0].target_position}, // 目标位置
{ZER_Device0, VID_PID, 0x60FF, 0, &zer_offsets[0].target_velocity}, // 目标速度
{ZER_Device0, VID_PID, 0x6060, 0, &zer_offsets[0].operation_mode}, // 运行模式
{ZER_Device0, VID_PID, 0x6041, 0, &zer_offsets[0].status_word}, // 状态字
{ZER_Device0, VID_PID, 0x6064, 0, &zer_offsets[0].position_actual_value}, // 实际位置
{ZER_Device0, VID_PID, 0x606C, 0, &zer_offsets[0].velocity_actual_value}, // 实际速度
{ZER_Device0, VID_PID, 0x603F, 0, &zer_offsets[0].error_code}, // 错误代码
{ZER_Device0, VID_PID, 0x6061, 0, &zer_offsets[0].modes_of_operation_display}, // 模式显示
{} // 结束标记
};
static ec_pdo_entry_info_t zer_device_pdo_entries[] = {
/*RxPdo 0x1600*/
{0x6040, 0x00, 16}, /* control word */
{0x607a, 0x00, 32}, /* target position */
{0x60ff, 0x00, 32}, /* target velocity */
{0x6060, 0x00, 8}, /* modes of operation */
/*TxPdo 0x1A00*/
{0x6041, 0x00, 16}, /* status word */
{0x6064, 0x00, 32}, /* position actual value */
{0x606c, 0x00, 32}, /* velocity actual value */
{0x603f, 0x00, 16}, /* error code */
{0x6061, 0x00, 8}, /* modes of operation display */
};
static ec_pdo_info_t zer_device_pdos[] = {
//RxPdo
{0x1600, 4, zer_device_pdo_entries + 0 },
//TxPdo
{0x1A00, 5, zer_device_pdo_entries + 4 }
};
static ec_sync_info_t zer_device_syncs[] = {
{0, EC_DIR_OUTPUT, 0, NULL, EC_WD_DISABLE},
{1, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
{2, EC_DIR_OUTPUT, 1, zer_device_pdos + 0, EC_WD_ENABLE},
{3, EC_DIR_INPUT, 1, zer_device_pdos + 1, EC_WD_DISABLE},
{0xff}
};

View File

@@ -0,0 +1,80 @@
#pragma once
#include "ecrt.h"
#include "ethercat_control/motor_control.hpp" // 电机控制库
#define ET_Device0 0,0 /*EtherCAT address on the bus 别名,位置*/
#define ET_VID_PID 0x00000009,0x26483062 /*Vendor ID, product code 厂商ID和产品代码*/
typedef struct {
unsigned int ctrl_word; // 0x6040:00
unsigned int target_position; // 0x607A:00
unsigned int target_velocity; // 0x60FF:00
unsigned int target_torque; // 0x6071:00
unsigned int max_torque; // 0x6072:00
unsigned int operation_mode; // 0x6060:00
unsigned int reserved1; // 0x5FFE:00
unsigned int status_word; // 0x6041:00
unsigned int position_actual_value; // 0x6064:00
unsigned int velocity_actual_value; // 0x606C:00
unsigned int torque_actual_value; // 0x6077:00
unsigned int error_code; // 0x603F:00
// unsigned int modes_of_operation_display; // 0x6061:00
// unsigned int reserved2; // 0x5FFE:00
} et_offset_t;
static et_offset_t et_offsets[NUM_SLAVES];
// ------------------- PDO 定义CSV/CSP/CST对应 0x1600/0x1A00 -------------------
// 下列结构由IGH主站 cstruct 自动生成
const static ec_pdo_entry_reg_t et_domain1_regs[] = {
{ET_Device0, ET_VID_PID, 0x6040, 0, &et_offsets[0].ctrl_word}, // 控制字
{ET_Device0, ET_VID_PID, 0x607A, 0, &et_offsets[0].target_position}, // 目标位置
{ET_Device0, ET_VID_PID, 0x60FF, 0, &et_offsets[0].target_velocity}, // 目标速度
{ET_Device0, ET_VID_PID, 0x6071, 0, &et_offsets[0].target_torque}, // 目标扭矩
{ET_Device0, ET_VID_PID, 0x6072, 0, &et_offsets[0].max_torque}, // 最大扭矩
{ET_Device0, ET_VID_PID, 0x6060, 0, &et_offsets[0].operation_mode}, // 运行模式
{ET_Device0, ET_VID_PID, 0x0000, 0, &et_offsets[0].reserved1}, // 保留字段1
{ET_Device0, ET_VID_PID, 0x6041, 0, &et_offsets[0].status_word}, // 状态字
{ET_Device0, ET_VID_PID, 0x6064, 0, &et_offsets[0].position_actual_value}, // 实际位置
{ET_Device0, ET_VID_PID, 0x606C, 0, &et_offsets[0].velocity_actual_value}, // 实际速度
{ET_Device0, ET_VID_PID, 0x6077, 0, &et_offsets[0].torque_actual_value}, // 实际扭矩)
{ET_Device0, ET_VID_PID, 0x603F, 0, &et_offsets[0].error_code}, // 错误代码
// {ET_Device0, ET_VID_PID, 0x6061, 0, &et_offsets[0].modes_of_operation_display}, // 模式显示
// {ET_Device0, ET_VID_PID, 0x5FF2, 0, &et_offsets[0].reserved2}, // 保留字段2
{} // 结束标记
};
static ec_pdo_entry_info_t et_device_pdo_entries[] = {
/*RxPdo 0x1600*/
{0x6040, 0x00, 16}, /* control word */
{0x607a, 0x00, 32}, /* target position */
{0x60ff, 0x00, 32}, /* target velocity */
{0x6071, 0x00, 16}, /* Target Torque */
{0x6072, 0x00, 16}, /* Max Torque */
{0x6060, 0x00, 8}, /* modes of operation */
{0x0000, 0x00, 8},
/*TxPdo 0x1A00*/
{0x6041, 0x00, 16}, /* status word */
{0x6064, 0x00, 32}, /* position actual value */
{0x606c, 0x00, 32}, /* velocity actual value */
{0x6077, 0x00, 16}, /* torque actual value */
{0x603f, 0x00, 16}, /* error code */
// {0x6061, 0x00, 8}, /* modes of operation display */
// {0x5ff2, 0x00, 8},
};
static ec_pdo_info_t et_device_pdos[] = {
//RxPdo
{0x1600, 7, et_device_pdo_entries + 0 },
//TxPdo
{0x1A00, 5, et_device_pdo_entries + 7 }
};
static ec_sync_info_t et_device_syncs[] = {
{0, EC_DIR_OUTPUT, 0, NULL, EC_WD_DISABLE},
{1, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
{2, EC_DIR_OUTPUT, 1, et_device_pdos + 0, EC_WD_ENABLE},
{3, EC_DIR_INPUT, 1, et_device_pdos + 1, EC_WD_DISABLE},
{0xff}
};

View File

@@ -0,0 +1,14 @@
#pragma once
#include <stdbool.h>
namespace ethercat_core {
// 初始化 EtherCAT请求 master、配置 PDO、激活、启动实时循环
// 返回 true 表示已启动/已在运行false 表示失败或重复启动。
bool start();
// 请求实时循环退出(由 Ctrl+C 回调调用)
void request_exit();
// (后续添加)停机:拉低 run_enable。实时线程不强退保持可再次启用。
void stop_soft();
}

View File

@@ -0,0 +1,81 @@
#pragma once
#include <array>
#include <atomic>
#include <cstdint>
#include <algorithm>
constexpr int NUM_SLAVES = 8; //定义电机数量
//运行模式
enum class OpMode : int8_t {
CSP = 8, // Cyclic Synchronous Position
CSV = 9, // Cyclic Synchronous Velocity
CST = 10, // Cyclic Synchronous Torque
PVT = 5 // PVT模式
};
// 周期线程与上层通讯的共享结构
// 电机命令
struct MotorCommand {
std::array<std::atomic<int8_t>, NUM_SLAVES> mode; // 运行模式
std::array<std::atomic<int32_t>, NUM_SLAVES> target_vel; // 目标速度
std::array<std::atomic<int32_t>, NUM_SLAVES> target_pos; // 目标位置
std::array<std::atomic<int16_t>, NUM_SLAVES> target_torque; // 目标扭矩
std::atomic<double> pvt_kp[NUM_SLAVES]; // PVT kp增益
std::atomic<double> pvt_kd[NUM_SLAVES]; // PVT kd增益
// 运行控制意图(周期线程据此决定是否写 0x001F
std::array<std::atomic<bool>, NUM_SLAVES> run_enable;
// 故障复位/急停意图(待后续添加)
std::array<std::atomic<bool>, NUM_SLAVES> fault_reset;
std::array<std::atomic<bool>, NUM_SLAVES> quick_stop;
};
// 电机状态
struct MotorState {
std::array<std::atomic<uint16_t>,NUM_SLAVES> status_word; // 从站状态字
std::array<std::atomic<int32_t>, NUM_SLAVES> pos_act; // 实际位置
std::array<std::atomic<int32_t>, NUM_SLAVES> vel_act; // 实际速度
std::array<std::atomic<int16_t>, NUM_SLAVES> torque_act; // 实际扭矩
std::array<std::atomic<int32_t>, NUM_SLAVES> pos_cmd; // 每个周期CSP的目标位置因速度限制由target_pos拆分而来
};
// 全局共享实例(由 .cpp 定义)
extern MotorCommand g_cmd;
extern MotorState g_state;
// ---------- 控制接口(供上层/ROS2 调用) ----------
// 初始化缺省值(上电后调用一次)
void mc_init_defaults(OpMode default_mode = OpMode::CSV);
// 设定单轴模式/目标值/运行意图
void mc_set_mode(int idx, OpMode mode);
void mc_set_target_velocity(int idx, int32_t vel);
void mc_set_target_position(int idx, int32_t pos);
void mc_set_target_torque(int idx, int16_t tq);
void mc_enable_run(int idx, bool enable);
// 批量设置电机指令
void mc_set_all_mode(OpMode mode);
void mc_set_all_target_velocity(int32_t vel);
void mc_enable_all(bool enable);
// 设置PVT参数
void mc_set_all_pvt_gains(double kp, double kd);
void mc_set_pvt_gains(int axis, double kp, double kd);
// 故障/急停意图(待后续添加)
void mc_fault_reset(int idx);
void mc_quick_stop(int idx);
// 状态读取(从共享状态快照中取,非直接读 PDO
void mc_get_state(int idx, uint16_t &status, int32_t &pos, int32_t &vel, int16_t &torque);
// 检查电机轴号是否合法
inline bool mc_valid_index(int idx) { return idx >= 0 && idx < NUM_SLAVES; }
// void mc_get_pos_cmd_and_act(int idx, int32_t &pos_cmd, int32_t &pos_act);
// void mc_print_pos_cmd_and_act(int idx); // 打印单轴
// void mc_print_all_pos_cmd_and_act(); // 打印全部

View File

@@ -0,0 +1,80 @@
#pragma once
#include "ecrt.h"
#include "ethercat_control/motor_control.hpp" // 电机控制库
#define ZER_Device0 0,0 /*EtherCAT address on the bus 别名,位置*/
#define ZER_VID_PID 0x5a65726f,0x00029252 /*Vendor ID, product code 厂商ID和产品代码*/
typedef struct {
unsigned int ctrl_word; // 0x6040:00
unsigned int target_position; // 0x607A:00
unsigned int target_velocity; // 0x60FF:00
unsigned int target_torque; // 0x6071:00
unsigned int max_torque; // 0x6072:00
unsigned int operation_mode; // 0x6060:00
unsigned int reserved1; // 0xf0ff:00
unsigned int status_word; // 0x6041:00
unsigned int position_actual_value; // 0x6064:00
unsigned int velocity_actual_value; // 0x606C:00
unsigned int torque_actual_value; // 0x6077:00
unsigned int error_code; // 0x603F:00
unsigned int modes_of_operation_display; // 0x6061:00
unsigned int reserved2; // 0xf0ff:00
} zer_offset_t;
static zer_offset_t zer_offsets[NUM_SLAVES];
// ------------------- PDO 定义CSV/CSP/CST对应 0x1600/0x1A00 -------------------
// 下列结构由IGH主站 cstruct 自动生成
const static ec_pdo_entry_reg_t zer_domain1_regs[] = {
{ZER_Device0, ZER_VID_PID, 0x6040, 0, &zer_offsets[0].ctrl_word}, // 控制字
{ZER_Device0, ZER_VID_PID, 0x607A, 0, &zer_offsets[0].target_position}, // 目标位置
{ZER_Device0, ZER_VID_PID, 0x60FF, 0, &zer_offsets[0].target_velocity}, // 目标速度
{ZER_Device0, ZER_VID_PID, 0x6071, 0, &zer_offsets[0].target_torque}, // 目标扭矩
{ZER_Device0, ZER_VID_PID, 0x6072, 0, &zer_offsets[0].max_torque}, // 最大扭矩
{ZER_Device0, ZER_VID_PID, 0x6060, 0, &zer_offsets[0].operation_mode}, // 运行模式
{ZER_Device0, ZER_VID_PID, 0xf0ff, 0, &zer_offsets[0].reserved1}, // 保留字段1
{ZER_Device0, ZER_VID_PID, 0x6041, 0, &zer_offsets[0].status_word}, // 状态字
{ZER_Device0, ZER_VID_PID, 0x6064, 0, &zer_offsets[0].position_actual_value}, // 实际位置
{ZER_Device0, ZER_VID_PID, 0x606C, 0, &zer_offsets[0].velocity_actual_value}, // 实际速度
{ZER_Device0, ZER_VID_PID, 0x6077, 0, &zer_offsets[0].torque_actual_value}, // 实际扭矩)
{ZER_Device0, ZER_VID_PID, 0x603F, 0, &zer_offsets[0].error_code}, // 错误代码
{ZER_Device0, ZER_VID_PID, 0x6061, 0, &zer_offsets[0].modes_of_operation_display}, // 模式显示
{ZER_Device0, ZER_VID_PID, 0xf0ff, 0, &zer_offsets[0].reserved2}, // 保留字段2
{} // 结束标记
};
static ec_pdo_entry_info_t zer_device_pdo_entries[] = {
/*RxPdo 0x1600*/
{0x6040, 0x00, 16}, /* control word */
{0x607a, 0x00, 32}, /* target position */
{0x60ff, 0x00, 32}, /* target velocity */
{0x6071, 0x00, 16}, /* Target Torque */
{0x6072, 0x00, 16}, /* Max Torque */
{0x6060, 0x00, 8}, /* modes of operation */
{0xf0ff, 0x00, 8},
/*TxPdo 0x1A00*/
{0x6041, 0x00, 16}, /* status word */
{0x6064, 0x00, 32}, /* position actual value */
{0x606c, 0x00, 32}, /* velocity actual value */
{0x6077, 0x00, 16}, /* torque actual value */
{0x603f, 0x00, 16}, /* error code */
{0x6061, 0x00, 8}, /* modes of operation display */
{0xf0ff, 0x00, 8},
};
static ec_pdo_info_t zer_device_pdos[] = {
//RxPdo
{0x1600, 7, zer_device_pdo_entries + 0 },
//TxPdo
{0x1A00, 7, zer_device_pdo_entries + 7 }
};
static ec_sync_info_t zer_device_syncs[] = {
{0, EC_DIR_OUTPUT, 0, NULL, EC_WD_DISABLE},
{1, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
{2, EC_DIR_OUTPUT, 1, zer_device_pdos + 0, EC_WD_ENABLE},
{3, EC_DIR_INPUT, 1, zer_device_pdos + 1, EC_WD_DISABLE},
{0xff}
};

View File

@@ -0,0 +1,24 @@
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import TimerAction
def generate_launch_description():
# realtime_env = {
# "RCUTILS_SCHEDULER_POLICY" : "SCHED_FIFO",
# "RCUTILS_THREAD_PRIORITY" : "90",
# "RCUTILS_CONSOLE_OUTPUT_FORMAT" : "{time}:[{name}]:{message}"
# }
# 创建节点启动描述
ethercat_node = Node(
package='ethercat_control', # 功能包名称
executable='ethercat_node', # 可执行文件名称
name='ethercat_node', # 节点名称(可自定义)
output='screen', # 输出到屏幕
parameters=[{"lock_memory": True, "thread_priority":90}] # 启用模拟时间
)
# 组装launch描述
return LaunchDescription([
ethercat_node
])

View File

@@ -0,0 +1,23 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>ethercat_control</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="you@example.com">orangepi</maintainer>
<license>TODO: License declaration</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rclcpp</depend>
<depend>std_msgs</depend>
<depend>sensor_msgs</depend>
<depend>rcl_interfaces</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>

View File

@@ -0,0 +1,265 @@
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <thread>
#include <pthread.h>
#include <sched.h>
#include <optional> // ✅ 新增
#include "motor_control.hpp"
// 由你现有源码提供:
void cyclic_task(); // 实时循环(已接入 g_cmd / g_state
void ethercat_after_init();// 可选:若你想在这里放 mc_init_defaults 等
// ---------- 模式解析(安全版):失败返回 nullopt不做任何电机操作 ----------
static std::optional<OpMode> parse_mode(const std::string &s){
if (s=="csv" || s=="CSV" || s=="vel" || s=="VEL" || s=="speed" || s=="SPEED")
return OpMode::CSV; // 9
if (s=="csp" || s=="CSP" || s=="pos" || s=="POS" || s=="position" || s=="POSITION")
return OpMode::CSP; // 8
if (s=="cst" || s=="CST" || s=="tor" || s=="TOR" || s=="torque" || s=="TORQUE")
return OpMode::CST; // 10
return std::nullopt; // ❗未知输入 -> 调用者处理,绝不默认 CSV
}
void start_rt_cyclic_thread(){
std::thread([]{
// 把实时优先级设置到线程上(不占用主线程)
sched_param param{};
param.sched_priority = sched_get_priority_max(SCHED_FIFO);
pthread_setschedparam(pthread_self(), SCHED_FIFO, &param);
cyclic_task(); // 阻塞
}).detach();
}
static void print_help(){
std::cout <<
R"(命令格式:
help
quit / exit 退
enable on|off [i|-1] 使/ -1
state [i] i 0..19
set <i> csp pos <val>; <j> csv vel <val>; <k> cst tor <val>
csv|vel|speedcsp|pos|positioncst|tor|torque
)"
<< std::endl;
}
// static void print_help(){
// std::cout <<
// R"(命令格式:
// help 显示帮助
// quit / exit 退出程序
// enable on|off [i|-1] 使能/禁止运行;省略或 -1 表示所有电机
// mode i csv|csp|cst 设置第 i 台的模式(速度/位置/扭矩)
// vel i value 设置第 i 台目标速度int32
// pos i value 设置第 i 台目标位置int32
// tor i value 设置第 i 台目标扭矩int16
// all vel value 所有电机目标速度
// state [i] 打印第 i 台状态(省略打印 0..19 摘要)
// set <i> csp pos <val>; <j> csv vel <val>; <k> cst tor <val> 同时设置多个轴
// 别名csv|vel|speedcsp|pos|positioncst|tor|torque
// )"
// << std::endl;
// }
void cli_command_loop(){
print_help();
std::string line;
while (std::cout << "> " && std::getline(std::cin, line)){
std::istringstream iss(line);
std::string cmd; iss >> cmd;
if (cmd.empty()) continue;
if (cmd=="help") { print_help(); continue; }
if (cmd=="quit" || cmd=="exit") { break; }
if (cmd=="enable"){
std::string onoff; int idx = -1;
iss >> onoff >> idx;
bool en = (onoff=="on" || onoff=="1" || onoff=="true");
if (!iss || idx==-1){
mc_enable_all(en);
std::cout << "[ALL] enable=" << en << "\n";
}else{
if (mc_valid_index(idx)){ mc_enable_run(idx,en); std::cout << "[S"<<idx<<"] enable="<<en<<"\n";}
else std::cout << "无效索引\n";
}
continue;
}
if (cmd=="mode"){
int idx; std::string m; iss >> idx >> m;
if (iss && mc_valid_index(idx)){
auto pm = parse_mode(m);
if (!pm){
std::cout << "无效模式: " << m << "(允许: csv/csp/cst 或 vel/pos/tor\n";
continue; // ❗不做任何电机操作
}
mc_set_mode(idx, *pm);
std::cout << "[S"<<idx<<"] mode="<<m<<"\n";
}else {
std::cout << "用法: mode i csv|csp|cst\n";
}
continue;
}
if (cmd=="vel"){
int idx; long v; iss >> idx >> v;
if (iss && mc_valid_index(idx)){
mc_set_target_velocity(idx, (int32_t)v);
std::cout << "[S"<<idx<<"] vel="<<v<<"\n";
}else std::cout << "用法: vel i value\n";
continue;
}
if (cmd=="pos"){
int idx; long p; iss >> idx >> p;
if (iss && mc_valid_index(idx)){
mc_set_target_position(idx, (int32_t)p);
std::cout << "[S"<<idx<<"] pos="<<p<<"\n";
}else std::cout << "用法: pos i value\n";
continue;
}
if (cmd=="tor"){
int idx; long t; iss >> idx >> t;
if (iss && mc_valid_index(idx)){
mc_set_target_torque(idx, (int16_t)t);
std::cout << "[S"<<idx<<"] tor="<<t<<"\n";
}else std::cout << "用法: tor i value\n";
continue;
}
if (cmd=="all"){
std::string what; long v;
iss >> what >> v;
if (what=="vel" && iss){
mc_set_all_target_velocity((int32_t)v);
std::cout << "[ALL] vel="<<v<<"\n";
}else{
std::cout << "用法: all vel value\n";
}
continue;
}
if (cmd == "set") { // 批量控制函数(先校验,后执行)
// 把这一行剩余内容整体取出来,然后按 ';' 切成多段
std::string rest;
std::getline(iss, rest);
if (rest.empty()) {
std::cout << "用法: set <i> csp pos <val>; <j> csv vel <val>; <k> cst tor <val>\n";
continue;
}
// 去掉可能的开头空格/结尾空格
auto ltrim = [](std::string& s){
size_t p = s.find_first_not_of(" \t");
if (p != std::string::npos) s.erase(0, p);
};
auto rtrim = [](std::string& s){
size_t p = s.find_last_not_of(" \t");
if (p != std::string::npos) s.erase(p+1);
};
// 按 ';' 分割子命令
std::vector<std::string> chunks;
{
std::stringstream ss(rest);
std::string piece;
while (std::getline(ss, piece, ';')) {
ltrim(piece); rtrim(piece);
if (!piece.empty()) chunks.push_back(piece);
}
}
if (chunks.empty()) {
std::cout << "用法: set <i> csp pos <val>; <j> csv vel <val>; <k> cst tor <val>\n";
continue;
}
// 逐个子命令解析与执行:⚠️ 先校验,再动作;出错则跳过该子命令
for (auto& one : chunks) {
std::istringstream si(one);
int idx = -1;
std::string mode_s, field;
long long val = 0;
si >> idx >> mode_s >> field >> val;
if (!si || !mc_valid_index(idx)) {
std::cout << "[set] 子命令格式错误或轴号越界: \"" << one << "\"\n";
continue; // ❗不做任何电机操作
}
// 解析模式
auto pm = parse_mode(mode_s);
if (!pm) {
std::cout << "[set] 未知模式: " << mode_s << " in \"" << one << "\"\n";
continue; // ❗不做任何电机操作
}
OpMode mode = *pm;
// 校验字段是否与模式匹配
bool ok_field = false;
if (mode == OpMode::CSP) {
ok_field = (field=="pos" || field=="position" || field=="POS" || field=="POSITION");
if (!ok_field) {
std::cout << "[set] CSP 需要字段: pos <val> Got \"" << field << "\"\n";
continue; // ❗不做任何电机操作
}
} else if (mode == OpMode::CSV) {
ok_field = (field=="vel" || field=="velocity" || field=="speed" ||
field=="VEL" || field=="VELOCITY" || field=="SPEED");
if (!ok_field) {
std::cout << "[set] CSV 需要字段: vel <val> Got \"" << field << "\"\n";
continue; // ❗不做任何电机操作
}
} else if (mode == OpMode::CST) {
ok_field = (field=="tor" || field=="torque" || field=="TOR" || field=="TORQUE");
if (!ok_field) {
std::cout << "[set] CST 需要字段: tor <val> Got \"" << field << "\"\n";
continue; // ❗不做任何电机操作
}
}
// —— 所有校验通过,才真正执行 —— //
mc_set_mode(idx, mode);
if (mode == OpMode::CSP) {
mc_set_target_position(idx, static_cast<int32_t>(val));
std::cout << "[S" << idx << "] mode=CSP pos=" << val << "\n";
} else if (mode == OpMode::CSV) {
mc_set_target_velocity(idx, static_cast<int32_t>(val));
std::cout << "[S" << idx << "] mode=CSV vel=" << val << "\n";
} else if (mode == OpMode::CST) {
mc_set_target_torque(idx, static_cast<int16_t>(val));
std::cout << "[S" << idx << "] mode=CST tor=" << val << "\n";
}
// 是否自动 enable 不在此处改变,保持你当前逻辑;如需可追加:
// mc_enable_run(idx, true);
}
continue;
}
if (cmd=="state"){
int idx=-1; iss >> idx;
if (!iss){ // 打印摘要
for (int i=0;i<NUM_SLAVES;++i){
uint16_t sw; int32_t pos,vel; int16_t tq;
mc_get_state(i, sw, pos, vel, tq);
std::cout << "[S"<<i<<"] SW=0x"<<std::hex<<sw<<std::dec
<<" pos="<<pos<<" vel="<<vel<<" tq="<<tq<<"\n";
}
}else if (mc_valid_index(idx)){
uint16_t sw; int32_t pos,vel; int16_t tq;
mc_get_state(idx, sw, pos, vel, tq);
std::cout << "[S"<<idx<<"] SW=0x"<<std::hex<<sw<<std::dec
<<" pos="<<pos<<" vel="<<vel<<" tq="<<tq<<"\n";
}else{
std::cout << "索引无效\n";
}
continue;
}
std::cout << "未知命令,输入 help 查看帮助\n";
}
std::cout << "退出命令行控制。\n";
}

View File

@@ -0,0 +1,614 @@
/*
* 程序功能为通过EtherCAT周期控制伺服电机
*
* 功能总览:
* 1) 初始化 EtherCAT Master / Domain / 从站配置PDO/同步管理/DC 时钟);
* 2) 注册 PDO 映射,建立 process image 的偏移offsets[]
* 3) 启动 1kHz 实时循环cyclic_task
* - 同步 DC 时钟,收发域数据;
* - 跟踪各轴 DS402 状态机Ready->Switched on->Operation enabled
* - 根据上层 g_cmdCSV/CSP/CST/PVT写入目标值到 PDO
*/
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>
#include <sys/mman.h>
#include <malloc.h>
#include <sched.h> /* sched_setscheduler() */
#include <thread>
#include <atomic>
#include <pthread.h>
#include <cmath> // for std::llround
#include <iostream>
#include "ethercat_control/zer_config.hpp"
#include "ethercat_control/et_config.hpp"
// #include "ethercat_control/ds_config.hpp"
//#include "cmd_interface.hpp" //命令行控制库
// sudo /opt/etherlab/bin/ethercat
// sudo /etc/init.d/ethercat start
// sudo dmesg | grep EtherCAT
// sudo /opt/etherlab/bin/ethercat cstruct -a 0
// ------------------- 应用参数 -------------------
#define FREQUENCY 125 //控制周期频率1000Hz
#define CLOCK_TO_USE CLOCK_MONOTONIC
//#define MEASURE_TIMING
//#define NUM_SLAVES 3 //从端数量在motor_control.hpp中定义
//#define TARGET_VALUE 10000 //目标位置、速度或者扭矩
#define CYCLIC_POSITION 8 //CSP 周期同步位置模式
#define CYCLIC_VELOCITY 9 //CSP 周期同步速度模式
#define CYCLIC_TORQUE 10 //CSP 周期同步扭矩模式
#define PVT_MODE 5 //PVT模式
#define CSP_MAX_VEL_COUNTS_PER_S 65536 //65536 //CSP最大速度计数/s
//#define CSP_MAX_VEL_COUNTS_PER_S 1 //低速测试CSP最大速度计数
#define CSP_POS_DEADBAND 10 //CSP允许的误差(计数)
#define NSEC_PER_SEC (1000000000L)
#define PERIOD_NS (NSEC_PER_SEC / FREQUENCY) //本次设置周期PERIOD_NS为1ms
#define DIFF_NS(A, B) (((B).tv_sec - (A).tv_sec) * NSEC_PER_SEC + \
(B).tv_nsec - (A).tv_nsec)
#define TIMESPEC2NS(T) ((uint64_t) (T).tv_sec * NSEC_PER_SEC + (T).tv_nsec)
static std::atomic<bool> g_started{false}; //g_started 是一个进程内的“已启动”标志
static std::atomic<bool> g_run{true}; //g_run是运行标志为1时电机可运行
// ------------------- EtherCAT 句柄 -------------------
static ec_master_t *master = NULL;
static ec_master_state_t master_state = {};
static ec_domain_t *domain1 = NULL;
static ec_domain_state_t domain1_state = {};
static ec_slave_config_t *sc[NUM_SLAVES] = {0};
static ec_slave_config_state_t sc_state[NUM_SLAVES] = {};
static uint8_t *domain1_pd = NULL; // process data
static unsigned int counter = 0;
static unsigned int sync_ref_counter = 0;
const struct timespec cycletime = {0, PERIOD_NS};
// ------------------- 工具函数 -------------------
// 相加时间并处理纳秒进位
struct timespec timespec_add(struct timespec time1, struct timespec time2)
{
struct timespec result;
if ((time1.tv_nsec + time2.tv_nsec) >= NSEC_PER_SEC) {
result.tv_sec = time1.tv_sec + time2.tv_sec + 1;
result.tv_nsec = time1.tv_nsec + time2.tv_nsec - NSEC_PER_SEC;
} else {
result.tv_sec = time1.tv_sec + time2.tv_sec;
result.tv_nsec = time1.tv_nsec + time2.tv_nsec;
}
return result;
}
//查看domain1工作状态
void check_domain1_state(void)
{
ec_domain_state_t ds;
ecrt_domain_state(domain1, &ds);
if (ds.working_counter != domain1_state.working_counter)
//printf("Domain1: WC %u.\n", ds.working_counter); //当数字变化,表示丢包
if (ds.wc_state != domain1_state.wc_state)
//printf("Domain1: State %u.\n", ds.wc_state);
domain1_state = ds;
}
//查看master工作状态
void check_master_state(void)
{
ec_master_state_t ms;
ecrt_master_state(master, &ms);
if (ms.slaves_responding != master_state.slaves_responding)
//printf("%u slave(s).\n", ms.slaves_responding);
if (ms.al_states != master_state.al_states)
//printf("AL states: 0x%02X.\n", ms.al_states);
if (ms.link_up != master_state.link_up)
//printf("Link is %s.\n", ms.link_up ? "up" : "down");
master_state = ms;
}
//查询并更新每个从站的在线/AL/operational 状态
void check_slave_config_states(void)
{
ec_slave_config_state_t s;
for (int i = 0; i < NUM_SLAVES; ++i) {
if (!sc[i]) continue;
ecrt_slave_config_state(sc[i], &s);
if (s.al_state != sc_state[i].al_state)
printf("[S%02d] State 0x%02X.\n", i, s.al_state);
if (s.online != sc_state[i].online)
printf("[S%02d] %s.\n", i, s.online ? "online" : "offline");
if (s.operational != sc_state[i].operational)
printf("[S%02d] %soperational.\n", i, s.operational ? "" : "Not ");
sc_state[i] = s;
}
}
// ------------------- 实时循环 -------------------
/**
* 1kHz 实时控制主循环
*
* 核心流程(每周期):
* 1) 绝对时钟休眠到下个周期;写入应用时间到主站;
* 2) 接收帧 / 处理域数据;
* 3) 遍历每个从站:
* - 读取 TxPDO状态字/实际位置/速度/扭矩/模式显示;
* - 把读到的实际值写回共享状态 g_state给上位机/ROS2
* - 读取上层命令 g_cmd期望模式、run_enable、pos/vel/tor、PVT kp/kd
* - 模式切换降级到perop→写 0x6060→初始化必要变量→进入下周期再跑
* - 跟踪 DS402 状态机,达到 Operation enabled 后根据模式写入:
* CSP写 0x607A
* CSV写 0x60FF
* CST写 0x6071
* PVT写 0x607A/0x60FF/0x6071 及 KP/KD(0x2000/0x2001)
* - run_enable=false安全处理速度清零、CSP 对齐当前位置、扭矩清零)。
* 4) 同步 DC 参考时钟与从站时钟;队列域数据并发送帧。
*
*/
int lastStatus = 0;
int motorCount1 = 0;
int motorCount2 = 0;
void cyclic_task()
{
struct timespec wakeupTime, time;
#ifdef MEASURE_TIMING
struct timespec startTime, endTime, lastStartTime = {};
uint32_t period_ns = 0, exec_ns = 0, latency_ns = 0,
latency_min_ns = 0, latency_max_ns = 0,
period_min_ns = 0, period_max_ns = 0,
exec_min_ns = 0, exec_max_ns = 0;
#endif
clock_gettime(CLOCK_TO_USE, &wakeupTime); //当前时间
static uint16_t command[NUM_SLAVES]; //状态字掩码
static uint16_t status[NUM_SLAVES]; //状态字
static uint16_t last_status[NUM_SLAVES]; //上个循环的状态字
static int inited = 0; //初始化
while(g_run.load(std::memory_order_relaxed)) { //主循环
wakeupTime = timespec_add(wakeupTime, cycletime);
clock_nanosleep(CLOCK_TO_USE, TIMER_ABSTIME, &wakeupTime, NULL); //使用绝对时间,精确等待下一个周期,避免循环执行的时间造成周期漂移
// Write application time to master
//
// It is a good idea to use the target time (not the measured time) as
// application time, because it is more stable.
//
ecrt_master_application_time(master, TIMESPEC2NS(wakeupTime)); // 更新主站应用时间
#ifdef MEASURE_TIMING
clock_gettime(CLOCK_TO_USE, &startTime);
latency_ns = DIFF_NS(wakeupTime, startTime);
period_ns = DIFF_NS(lastStartTime, startTime);
exec_ns = DIFF_NS(lastStartTime, endTime);
lastStartTime = startTime;
if (latency_ns > latency_max_ns) {
latency_max_ns = latency_ns;
}
if (latency_ns < latency_min_ns) {
latency_min_ns = latency_ns;
}
if (period_ns > period_max_ns) {
period_max_ns = period_ns;
}
if (period_ns < period_min_ns) {
period_min_ns = period_ns;
}
if (exec_ns > exec_max_ns) {
exec_max_ns = exec_ns;
}
if (exec_ns < exec_min_ns) {
exec_min_ns = exec_ns;
}
#endif
// receive process data
ecrt_master_receive(master); //接收 EtherCAT 帧
ecrt_domain_process(domain1); //处理域数据
// check process data state (optional)
check_domain1_state(); //检查域状态
if (counter) {
counter--;
} else { // do this at 1 Hz
counter = FREQUENCY;
// check for master state (optional)
check_master_state(); //检查主站状态
// check for slave configuration state(s)
check_slave_config_states(); //检查从站状态
#ifdef MEASURE_TIMING
// output timing stats
printf("period %10u ... %10u\n",
period_min_ns, period_max_ns);
printf("exec %10u ... %10u\n",
exec_min_ns, exec_max_ns);
printf("latency %10u ... %10u\n",
latency_min_ns, latency_max_ns);
printf("status word: 0x%4x\n",status);
period_max_ns = 0;
period_min_ns = 0xffffffff;
exec_max_ns = 0;
exec_min_ns = 0xffffffff;
latency_max_ns = 0;
latency_min_ns = 0xffffffff;
#endif
}
if (!inited) {
for (int i = 0; i < NUM_SLAVES; ++i) {
command[i] = 0x004F;
status[i] = 0x000F;
last_status[i] = status[i];
}
inited = 1;
}
static uint8_t csp_initialized[NUM_SLAVES] = {0}; // 进入CSP后是否已对齐过目标防止冲击
static uint8_t last_mode_cmd_inited = 0;
static int8_t last_mode_cmd[NUM_SLAVES] = {CYCLIC_VELOCITY}; // 记录上周期的期望模式默认为CSV
// 读取从站状态字、当前位置、速度、扭矩、运行模式
for (int i = 0; i < NUM_SLAVES; ++i)
{
uint16_t sw = EC_READ_U16(domain1_pd + zer_offsets[i].status_word);
int32_t pv = EC_READ_S32(domain1_pd + zer_offsets[i].position_actual_value);
int32_t vv = EC_READ_S32(domain1_pd + zer_offsets[i].velocity_actual_value);
int16_t tv = EC_READ_S16(domain1_pd + zer_offsets[i].torque_actual_value);
int8_t md = EC_READ_S8 (domain1_pd + zer_offsets[i].modes_of_operation_display);
status[i] = sw; //侦测子站状态字变化
if (status[i] != last_status[i]) {
// printf("[S%02d] 状态改变为: 0x%04X\n", i, status[i]);
last_status[i] = status[i];
}
// ----- 1) 写共享状态供ROS2 读取)-----
g_state.status_word[i].store(sw, std::memory_order_relaxed);
g_state.pos_act[i].store(pv, std::memory_order_relaxed);
g_state.vel_act[i].store(vv, std::memory_order_relaxed);
g_state.torque_act[i].store(tv, std::memory_order_relaxed);
// ----- 2) 读取本周期命令 -----
// 读CSP/CSV/CST/PVT 命令
const int8_t mode_cmd = g_cmd.mode[i].load(std::memory_order_relaxed);
const bool run_enable = g_cmd.run_enable[i].load(std::memory_order_relaxed);
const int32_t vel_cmd = g_cmd.target_vel[i].load(std::memory_order_relaxed);
const int32_t pos_cmd = g_cmd.target_pos[i].load(std::memory_order_relaxed);
const int16_t tor_cmd = g_cmd.target_torque[i].load(std::memory_order_relaxed);
//EC_WRITE_S8 (domain1_pd + offsets[i].operation_mode, mode_cmd); //模式设定
// 第一次运行时把 last_mode_cmd 初始化为实际显示模式,避免“假沿”
if (!last_mode_cmd_inited) {
last_mode_cmd[i] = md;
if (i == NUM_SLAVES - 1) last_mode_cmd_inited = 1;
}
if (md != mode_cmd) {
// 1) 拉到 Ready to switch on允许改模式
EC_WRITE_U16(domain1_pd + zer_offsets[i].ctrl_word, 0x0006);
// 2) 写目标模式
EC_WRITE_S8 (domain1_pd + zer_offsets[i].operation_mode, mode_cmd);
// 3) 下一周期状态机会自动从 0x0021→0x0023→0x0027
// 进入新模式时做必要初始化
bool csp_mode = (mode_cmd == CYCLIC_POSITION);
if (csp_mode) {
// 初次进入CSP把目标位置对齐当前实际位置避免冲击
// g_cmd.target_pos[i].store(pv, std::memory_order_relaxed); //把电机当前位置更新进上层
// EC_WRITE_S32(domain1_pd + zer_offsets[i].target_position, pv);
csp_initialized[i] = 1;
} else {
csp_initialized[i] = 0;
}
last_mode_cmd[i] = mode_cmd;
continue; // 本周期先不再下其他目标,等模式生效
}
if ((status[i]&0x006f)==0x0008){
uint16_t err = EC_READ_U16(domain1_pd + zer_offsets[i].error_code);
EC_WRITE_U16(domain1_pd + zer_offsets[i].ctrl_word, 0x0080);
std::cout << "clear : " << i << " " << err << std::endl;
}
// continue;
// DS402 状态机 & 写控制/目标
if ((status[i] & command[i]) == 0x0001 || (status[i] & command[i]) == 0x0040) {
EC_WRITE_U16(domain1_pd + zer_offsets[i].ctrl_word, 0x0006); // Ready to switch on
command[i] = 0x006F;
//std::cout << " Motor : " << i << " Ready to switch on" << std::endl;
} else if ((status[i] & command[i]) == 0x0021) {
EC_WRITE_U16(domain1_pd + zer_offsets[i].ctrl_word, 0x0007); // Switched on
command[i] = 0x006F;
// 同步当前位置为目标位置
int32_t pv2 = EC_READ_S32(domain1_pd + zer_offsets[i].position_actual_value);
EC_WRITE_S32(domain1_pd + zer_offsets[i].target_position, pv2);
g_cmd.target_pos[i].store(pv2, std::memory_order_relaxed);
motorCount1 ++;
//std::cout << " Motor : " << i << " times : " << motorCount1 << " Switched on" << std::endl;
} else if ((status[i] & command[i]) == 0x0023) {
EC_WRITE_U16(domain1_pd + zer_offsets[i].ctrl_word, 0x000f); // Operation enabled
command[i] = 0x006F;
//std::cout << " Motor : " << i << " Operation enabled" << std::endl;
} else if ((status[i] & command[i]) == 0x0027) {
// ---- 4) 运行态写目标(按模式/运行意图) ----
if (run_enable) {
//EC_WRITE_U16(domain1_pd + offsets[i].ctrl_word, 0x001F); // 仅在PP模式下需要
switch (mode_cmd) {
case CYCLIC_VELOCITY: // 9
EC_WRITE_S32(domain1_pd + zer_offsets[i].target_velocity, vel_cmd);
break;
case CYCLIC_POSITION: { // 8
// ---- 绝对式目标生成,不再用 pv 做增量基准 ----
// 每轴记住“上次下发的命令位置”
static int32_t csp_cmd_pos[NUM_SLAVES]; // 上次下发到 0x607A 的值
static uint8_t csp_inited[NUM_SLAVES] = {0}; // 是否已初始化
// 定点限速累计器:解决 v_max < FREQUENCY 时整除为 0 的问题
static int64_t vmax_acc[NUM_SLAVES] = {0}; // 单位counts/s 累加器
// 进入/首次运行:把命令位置对齐到“当前实际位置”,避免冲击
if (!csp_inited[i] || last_mode_cmd[i] != CYCLIC_POSITION) {
csp_cmd_pos[i] = pv; // 先对齐
vmax_acc[i] = 0;
csp_inited[i] = 1;
}
// 期望的“最终绝对目标”
const int32_t goal = g_cmd.target_pos[i].load(std::memory_order_relaxed);
// 本周期允许的最大步长counts/周期),用定点累加保证平均速度上限严格等于 CSP_MAX_VEL_COUNTS_PER_S
vmax_acc[i] += (int64_t)CSP_MAX_VEL_COUNTS_PER_S; // +v [counts/s]
int32_t max_step = (int32_t)(vmax_acc[i] / FREQUENCY); // 下取整,得到本周期可用步长
vmax_acc[i] -= (int64_t)max_step * FREQUENCY; // 保留余数到下周期
// 相对“命令轨迹”的误差(不是相对 pv
const int32_t err_cmd = goal - csp_cmd_pos[i];
// 死区:足够近就贴合到 goal
if (err_cmd > -CSP_POS_DEADBAND && err_cmd < CSP_POS_DEADBAND) {
csp_cmd_pos[i] = goal;
} else {
// 限速迈步:命令轨迹只按时间往 goal 逼近
if (err_cmd > 0) csp_cmd_pos[i] += (err_cmd > max_step) ? max_step : err_cmd;
else csp_cmd_pos[i] += (err_cmd < -max_step) ? -max_step : err_cmd;
}
// 可选:跟随误差防护(避免 following error 过大时继续“加命令”)
// const int32_t follow_err = csp_cmd_pos[i] - pv;
// const int32_t FE_LIMIT = (int32_t) (你的驱动跟随误差阈值); // 参考驱动参数
// if (std::abs(follow_err) > FE_LIMIT) {
// // 例如:冻结或减半 max_step给驱动时间追上
// }
// 下发“绝对目标”(不是增量)
EC_WRITE_S32(domain1_pd + zer_offsets[i].target_position, csp_cmd_pos[i]);
// 回显给上层(/joint_states_cmd 用)
g_state.pos_cmd[i].store(csp_cmd_pos[i], std::memory_order_relaxed);
break;
}
case CYCLIC_TORQUE: // 10
EC_WRITE_S16(domain1_pd + zer_offsets[i].target_torque, tor_cmd);
break;
default:
// 未知模式:默认速度模式安全置 0
//EC_WRITE_S32(domain1_pd + offsets[i].target_velocity, 0);
break;
}
} else {
// run_enable=false清零目标
EC_WRITE_U16(domain1_pd + zer_offsets[i].ctrl_word, 0x0007);
// CSV 安全:速度清零
EC_WRITE_S32(domain1_pd + zer_offsets[i].target_velocity, 0);
// CSP 安全:目标位置=当前位置 -> 立即“冻结”
int32_t pv_hold = EC_READ_S32(domain1_pd + zer_offsets[i].position_actual_value);
EC_WRITE_S32(domain1_pd + zer_offsets[i].target_position, pv_hold);
// 可选:扭矩模式也清零(最好维持急停状态,待修改)
EC_WRITE_S16(domain1_pd + zer_offsets[i].target_torque, 0);
EC_WRITE_U16(domain1_pd + zer_offsets[i].ctrl_word, 0x0007);//切换 switched on 状态
}
}
}
if (sync_ref_counter) {
sync_ref_counter--;
} else {
sync_ref_counter = 1; // sync every 2 cycle
clock_gettime(CLOCK_TO_USE, &time);
ecrt_master_sync_reference_clock_to(master, TIMESPEC2NS(time)); //每隔一个周期同步时钟
}
ecrt_master_sync_slave_clocks(master);
// send process data
ecrt_domain_queue(domain1); //排队域数据
ecrt_master_send(master); //发送ETHERCAT帧
#ifdef MEASURE_TIMING
clock_gettime(CLOCK_TO_USE, &endTime);
#endif
}
}
// ------------------- 启动/停机接口 -------------------
namespace ethercat_core {
//启动实时线程
static void start_rt_cyclic_thread(){
std::thread([]{
// 锁内存 & 实时优先级
mlockall(MCL_CURRENT | MCL_FUTURE); //将当前进程使用的所有内存MCL_CURRENT以及将来可能分配的所有内存MCL_FUTURE都锁定在物理 RAM 中禁止将其交换到磁盘Swap
sched_param param{}; param.sched_priority = 90 ; //sched_get_priority_max(SCHED_FIFO); //; //将当前线程的调度策略设置为 SCHED_FIFO (First-In, First-Out),并赋予最高优先级
pthread_setschedparam(pthread_self(), SCHED_FIFO, &param);
cyclic_task(); // 进入控制循环(阻塞)
}).detach();
}
/**
* 初始化 EtherCAT master / domain / PDO / DC 并启动实时循环
* return true 成功false 失败(会复位 g_started 标志)
*
* 步骤:
* 1) ecrt_request_master / ecrt_master_create_domain
* 2) 根据 use_pvt 选择配置 device_syncs 或 device_syncs_pvt
* 3) ecrt_slave_config_dc 启用 DC 同步;
* 4) ecrt_domain_reg_pdo_entry_list 注册 PDO entry保存 offsets[];
* 5) ecrt_master_activate + ecrt_domain_data 获取 process image 指针;
* 6) 设定上层默认模式PVT 或 CSV拉起 run_enable
* 7) 启动实时线程。
*/
bool start()
{
printf("Starting EtherCAT master...\n");
if (g_started.exchange(true)) {
// 已经启动
return true;
}
// 申请 master / 域
master = ecrt_request_master(0);
if (!master) {
perror("ecrt_request_master");
g_started.store(false); return false;
}
domain1 = ecrt_master_create_domain(master);
if (!domain1){
perror("ecrt_master_create_domain");
g_started.store(false); return false;
}
// 从站配置
for (int i = 0; i < NUM_SLAVES; ++i) {
std::cout << "Configuring slave " << i << "..." << std::endl;
int position = i+1;
sc[i] = ecrt_master_slave_config(master, 0, position, ZER_VID_PID);
if (!sc[i])
{
std::cout << "Failed slave cfg at pos " << i << std::endl;
// fprintf(stderr,"Failed slave cfg at pos %d\n", i); g_started.store(false); return false;
}
if (ecrt_slave_config_pdos(sc[i], EC_END, zer_device_syncs))
{
std::cout << "Failed PDO config " << i << std::endl;
// fprintf(stderr,"[S%02d] Failed PDO config\n", i); g_started.store(false); return false;
}
// DC配置
ecrt_slave_config_dc(sc[i], 0x0300, PERIOD_NS, 200, 0, 0);
}
// 注册 PDO entry 偏移
for (int i = 0; i < NUM_SLAVES; ++i) {
int position = i+1;
// ---- CSP/CSV/CST PDO ----
const size_t N = sizeof(zer_domain1_regs)/sizeof(zer_domain1_regs[0]);
ec_pdo_entry_reg_t regs[N];
memcpy(regs, zer_domain1_regs, sizeof(zer_domain1_regs));
for (size_t j = 0; j + 1 < N; ++j)
{
regs[j].alias = 0;
regs[j].position = position;
}
regs[0].offset = &zer_offsets[i].ctrl_word;
regs[1].offset = &zer_offsets[i].target_position;
regs[2].offset = &zer_offsets[i].target_velocity;
regs[3].offset = &zer_offsets[i].target_torque;
regs[4].offset = &zer_offsets[i].max_torque;
regs[5].offset = &zer_offsets[i].operation_mode;
regs[6].offset = &zer_offsets[i].reserved1;
regs[7].offset = &zer_offsets[i].status_word;
regs[8].offset = &zer_offsets[i].position_actual_value;
regs[9].offset = &zer_offsets[i].velocity_actual_value;
regs[10].offset = &zer_offsets[i].torque_actual_value;
regs[11].offset = &zer_offsets[i].error_code;
regs[12].offset = &zer_offsets[i].modes_of_operation_display;
regs[13].offset = &zer_offsets[i].reserved2;
if (ecrt_domain_reg_pdo_entry_list(domain1, regs)) {
fprintf(stderr, "[S%02d] PDO entry reg failed\n", i);
g_started.store(false); return false;
}
}
// 5) 激活 & 取 process image 指针
if (ecrt_master_activate(master)) { perror("ecrt_master_activate"); g_started.store(false); return false; }
domain1_pd = ecrt_domain_data(domain1);
if (!domain1_pd)
{
fprintf(stderr,"domain1_pd null\n");
g_started.store(false);
return false;
}
std::cout << "Starting EtherCAT master done." << std::endl;
mc_init_defaults(OpMode::CSP);
mc_enable_all(true);
// mc_set_all_target_velocity(0);
// 启动实时循环线程
start_rt_cyclic_thread();
return true;
}
void stop_soft(){
// 软停:拉低 run_enable保持 S7 switched on速度清零
mc_enable_all(false);
}
void request_exit(){
g_run.store(false); // 让循环跳出
}
} // namespace ethercat_core

View File

@@ -0,0 +1,166 @@
#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/string.hpp>
#include <sstream>
#include <iomanip>
#include <cmath>
#include <chrono>
#include <string>
#include <vector>
using namespace std::chrono_literals;
class PosRampNode : public rclcpp::Node {
public:
PosRampNode() : Node("pos_ramp_node"),
steady_clock_(RCL_STEADY_TIME)
{
// 参数
declare_parameter<int>("num_joints", 6); // 轴数
declare_parameter<int>("start_pos", 0); // 起始位置(计数)
declare_parameter<int>("end_pos", 2000); // 终点位置(计数)
declare_parameter<double>("v_max", 8000.0); // 最大速度counts/s
declare_parameter<double>("a_max", 20000.0); // 最大加速度counts/s^2
declare_parameter<double>("pause_sec", 5.0); // 暂停时长s
declare_parameter<int>("rate_hz", 100); // 发布频率Hz
get_parameter("num_joints", num_joints_);
get_parameter("start_pos", start_pos_);
get_parameter("end_pos", end_pos_);
get_parameter("v_max", v_max_);
get_parameter("a_max", a_max_);
get_parameter("pause_sec", pause_sec_);
get_parameter("rate_hz", rate_hz_);
if (rate_hz_ <= 0) rate_hz_ = 100;
dt_ = 1.0 / static_cast<double>(rate_hz_);
pub_ = create_publisher<std_msgs::msg::String>("ethercat/set", 10);
// 轨迹状态初始化
phase_ = Phase::MoveOut;
pos_ = static_cast<double>(start_pos_);
vel_ = 0.0;
goal_ = static_cast<double>(end_pos_);
last_ = steady_clock_.now();
timer_ = create_wall_timer(
std::chrono::milliseconds(static_cast<int>(1000.0 / rate_hz_)),
std::bind(&PosRampNode::onTimer, this));
RCLCPP_INFO(get_logger(),
"pos_ramp_node started: num_joints=%d, start=%d, end=%d, v_max=%.1f, a_max=%.1f, pause=%.1fs, rate=%d Hz",
num_joints_, start_pos_, end_pos_, v_max_, a_max_, pause_sec_, rate_hz_);
}
private:
enum class Phase { MoveOut, HoldOut, MoveBack, HoldStart };
void onTimer() {
// 用稳态时钟计算 dt尽量跟墙钟定时器保持一致
const rclcpp::Time now = steady_clock_.now();
double dt = (now - last_).seconds();
last_ = now;
if (dt <= 0.0) dt = dt_; // 回退到标称步长
// 为了轨迹稳定,限制 dt 的异常尖峰
if (dt > 3 * dt_) dt = dt_;
switch (phase_) {
case Phase::MoveOut: stepMotion(+1, dt, static_cast<double>(end_pos_), Phase::HoldOut); break;
case Phase::HoldOut: stepHold(dt, Phase::MoveBack, static_cast<double>(start_pos_)); break;
case Phase::MoveBack: stepMotion(-1, dt, static_cast<double>(start_pos_), Phase::HoldStart);break;
case Phase::HoldStart: stepHold(dt, Phase::MoveOut, static_cast<double>(end_pos_)); break;
}
// 发布到 ethercat/set对所有轴发相同位置
const int pos_i = static_cast<int>(std::llround(pos_));
std::ostringstream ss;
for (int i = 0; i < num_joints_; ++i) {
if (i) ss << "; ";
ss << i << " pos " << pos_i;
}
std_msgs::msg::String msg;
msg.data = ss.str();
pub_->publish(msg);
}
// 运动步进:梯形速度(加速/匀速/减速),避免超调,平滑到达 goal
void stepMotion(int dir, double dt, double goal_target, Phase next_hold_phase) {
const double s_rem = (goal_target - pos_); // 剩余位移
const double sign = (dir >= 0) ? 1.0 : -1.0;
const double a = a_max_ * sign;
// 需要的减速距离 d = v^2 / (2a_max)
const double d_decel = (vel_*vel_) / (2.0 * a_max_ + 1e-9);
// 决策:加速还是减速(或保持)
if (sign * s_rem <= d_decel) {
// 该减速了
vel_ -= a_max_ * sign * dt;
// 过零保护
if (sign * vel_ < 0.0) vel_ = 0.0;
} else {
// 加速直到 v_max
vel_ += a * dt;
const double v_lim = v_max_;
if (std::fabs(vel_) > v_lim) vel_ = v_lim * sign;
}
// 积分位置
pos_ += vel_ * dt;
// 过冲保护:若越过目标,截断到目标并进入下一相位
if ((sign > 0.0 && pos_ >= goal_target) ||
(sign < 0.0 && pos_ <= goal_target)) {
pos_ = goal_target;
vel_ = 0.0;
phase_ = next_hold_phase;
hold_t_ = 0.0;
goal_ = (next_hold_phase == Phase::HoldOut)
? static_cast<double>(end_pos_)
: static_cast<double>(start_pos_);
RCLCPP_INFO_THROTTLE(get_logger(), *get_clock(), 1000,
"Reached %.3f, entering hold.", goal_target);
}
}
// 暂停相位:计时到 pause_sec_ 后切换到下一运动相位,并设置新目标
void stepHold(double dt, Phase next_move_phase, double next_goal) {
hold_t_ += dt;
pos_ = goal_; // 保持
vel_ = 0.0;
if (hold_t_ >= pause_sec_) {
phase_ = next_move_phase;
goal_ = next_goal;
RCLCPP_INFO_THROTTLE(get_logger(), *get_clock(), 1000,
"Leaving hold, next goal=%.3f", next_goal);
}
}
// ---- members ----
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr pub_;
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Clock steady_clock_;
rclcpp::Time last_;
int num_joints_{6};
int start_pos_{0};
int end_pos_{2000};
int rate_hz_{100};
double v_max_{8000.0};
double a_max_{20000.0};
double pause_sec_{5.0};
double dt_{0.01};
Phase phase_{Phase::MoveOut};
double pos_{0.0};
double vel_{0.0};
double goal_{0.0};
double hold_t_{0.0};
};
int main(int argc, char **argv) {
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<PosRampNode>());
rclcpp::shutdown();
return 0;
}

View File

@@ -0,0 +1,420 @@
/**
* ROS2 节点:与 EtherCAT 周期线程共享指令/状态,提供文本话题指令解析、
* JointState 发布、CSP 指令回显与 CSV 记录、超时看门狗等。
*
* 主要功能:
* - 启动底层 EtherCAT 周期线程(见 ethercat_core::start
* - 订阅字符串命令话题,解析并写入 MotorCommandg_cmd
* - 周期发布:
* * joint_states实际位置/速度/扭矩)
* * joint_states_cmdCSP 目标位置的回显)
* * csp_table时间戳+每轴的 csp_cmd/real/err
* - 将表格同时写入 CSV 文件
* - 看门狗:一段时间未收到上层命令则将速度/扭矩清零
*
* 使用示例:
* - 切到 CSP 并给 0 号轴位置:
* ros2 topic pub /ethercat/set std_msgs/msg/String "data: '0 pos 10000'"
* - 切到 CSV 并给 0 号轴速度:
* ros2 topic pub /ethercat/set std_msgs/msg/String "data: '0 vel 2000'"
* - PVT 模式设置与给值(多条用分号隔开):
* ros2 topic pub --rate 10 /ethercat/set std_msgs/msg/String \
* "data: '0 pvt; 0 kp 5; 0 kd 5; 0 pvtpos 10000; 0 pvtvel 2000; 0 pvttor 0'"
*/
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
#include "sensor_msgs/msg/joint_state.hpp"
#include "ethercat_control/ethercat_core.hpp"
#include "ethercat_control/motor_control.hpp"
#include <sstream>
#include <cmath>
#include <fstream>
#include <iomanip>
#include <filesystem>
using std::placeholders::_1;
class EthercatNode : public rclcpp::Node {
public:
/**
* 构造函数:初始化 EtherCAT、参数、话题与定时器
*
* 过程:
* 1) 调用 ethercat_core::start() 启动底层主站与 1kHz 周期线程
* 2) 读取参数 num_joints/csv_path
* 3) 创建订阅(/ethercat/set与发布者joint_states / joint_states_cmd / csp_table
* 4) 创建 100Hz 的状态发布定时器
* 5) 打开 CSV 并写表头
* 6) 启动看门狗定时器100ms 检查一次)
*
*/
EthercatNode() : Node("ethercat_node") {
// 启动 EtherCAT 周期线程
if (!ethercat_core::start()) {
RCLCPP_FATAL(this->get_logger(), "EtherCAT core start failed.");
throw std::runtime_error("ethercat start failed");
}
// 关节名(给 JointState 用)
declare_parameter<int>("num_joints", NUM_SLAVES);
get_parameter("num_joints", num_joints_);
joint_names_.resize(num_joints_);
for (int i = 0; i < num_joints_; ++i) {
joint_names_[i] = "joint_" + std::to_string(i);
}
// CSV 文件路径参数,默认存到工作目录下
declare_parameter<std::string>("csv_path", "csp_log.csv");
get_parameter("csv_path", csv_path_);
// 订阅 set 文本命令:例如 "set 0 vel 1000; 1 pos 3000"
set_sub_ = create_subscription<std_msgs::msg::String>(
"ethercat/set", 10, std::bind(&EthercatNode::onSet, this, _1));
// 发布 JointState100Hz
js_pub_ = create_publisher<sensor_msgs::msg::JointState>("ec_joint_states", 10);
js_cmd_pub_ = create_publisher<sensor_msgs::msg::JointState>("ec_joint_states_cmd", 10);
// 表格话题把“轴i csp指令值/真实位置/误差”拼成一行
csp_table_pub_ = create_publisher<std_msgs::msg::String>("csp_table", 10);
// 定时器:每 100ms 发布一次状态
js_timer_ = create_wall_timer(
std::chrono::milliseconds(100),
std::bind(&EthercatNode::publishJointState, this));
// 打开 CSV 并写表头
// open_csv_and_write_header();
start_time_steady_ = steady_clock_.now();
RCLCPP_INFO(get_logger(), "ethercat_node ready.");
// 看门狗每100ms检测指令是否收到
last_command_time_ = this->get_clock()->now();
watchdog_timer_ = this->create_wall_timer(
std::chrono::milliseconds(100),
std::bind(&EthercatNode::watchdogCallback, this));
}
// 析构时收尾
~EthercatNode() override {
// if (csv_.is_open()) {
// csv_.flush();
// csv_.close();
// }
}
private:
void onSet(const std_msgs::msg::String::SharedPtr msg) {
last_command_time_ = this->get_clock()->now(); //刷新看门狗时间
// 对读到的控制指令进行处理
// 简易去空白工具
auto ltrim=[](std::string& s){ auto p=s.find_first_not_of(" \t"); if(p!=std::string::npos) s.erase(0,p); };
auto rtrim=[](std::string& s){ auto p=s.find_last_not_of(" \t"); if(p!=std::string::npos) s.erase(p+1); };
std::stringstream ss(msg->data); //整条命令
std::string chunk; //单条命令(整条命令切割而来)
while (std::getline(ss, chunk, ';')) { //将指令用;分割
ltrim(chunk); rtrim(chunk);
if (chunk.empty()) continue; //空段跳过
std::istringstream si(chunk);
int idx = -1; // 轴号
std::string kind; // 操作类型
std::string vstr; // 操作值
si >> idx >> kind; //第一个token为轴号第二个token为操作类型
if (!si || !mc_valid_index(idx)) {
// RCLCPP_WARN(get_logger(), "[set] bad: '%s'", chunk.c_str());
continue;
}
// 第三个Token为参数值
if (!(si >> vstr)) vstr.clear();
// 转换函数 字符串转换为long long和double形式
auto to_ll = [](const std::string& s)->long long {
try { size_t pos=0; long long v = std::stoll(s, &pos, 0); (void)pos; return v; }
catch (...) { return 0LL; }
};
auto to_d = [](const std::string& s)->double {
try { size_t pos=0; double v = std::stod(s, &pos); (void)pos; return v; }
catch (...) { return 0.0; }
};
// ======= PVT 指令解析处理 =======
if (kind == "pvt") {
mc_set_mode(idx, OpMode::PVT);
RCLCPP_INFO(get_logger(), "[S%d] switch to PVT mode", idx);
continue;
}
else if (kind == "kp" || kind == "pvt_kp") {
if (vstr.empty()) { RCLCPP_WARN(get_logger(), "[S%d] kp needs a value", idx); continue; }
const double kp = to_d(vstr);
// 取当前 kd避免把 kd 覆盖没了
const double kd_cur = g_cmd.pvt_kd[idx].load(std::memory_order_relaxed);
mc_set_pvt_gains(idx, kp, kd_cur);
RCLCPP_INFO(get_logger(), "[S%d] PVT_KP=%.6f (KD=%.6f keep)", idx, kp, kd_cur);
continue;
}
else if (kind == "kd" || kind == "pvt_kd") {
if (vstr.empty()) { RCLCPP_WARN(get_logger(), "[S%d] kd needs a value", idx); continue; }
const double kd = to_d(vstr);
// 取当前 kp避免把 kp 覆盖没了
const double kp_cur = g_cmd.pvt_kp[idx].load(std::memory_order_relaxed);
mc_set_pvt_gains(idx, kp_cur, kd);
RCLCPP_INFO(get_logger(), "[S%d] PVT_KD=%.6f (KP=%.6f keep)", idx, kd, kp_cur);
continue;
}
// 在 PVT 模式下下发目标(为了简单,提供带前缀的别名)
else if (kind == "pvtpos" || kind == "pvt_pos") {
if (vstr.empty()) { RCLCPP_WARN(get_logger(), "[S%d] pvtpos needs a value", idx); continue; }
mc_set_mode(idx, OpMode::PVT);
mc_set_target_position(idx, static_cast<int32_t>(to_ll(vstr)));
RCLCPP_INFO(get_logger(), "[S%d] PVT pos=%s", idx, vstr.c_str());
continue;
}
else if (kind == "pvtvel" || kind == "pvt_vel") {
if (vstr.empty()) { RCLCPP_WARN(get_logger(), "[S%d] pvtvel needs a value", idx); continue; }
mc_set_mode(idx, OpMode::PVT);
mc_set_target_velocity(idx, static_cast<int32_t>(to_ll(vstr)));
RCLCPP_INFO(get_logger(), "[S%d] PVT vel=%s", idx, vstr.c_str());
continue;
}
else if (kind == "pvttor" || kind == "pvt_tor") {
if (vstr.empty()) { RCLCPP_WARN(get_logger(), "[S%d] pvttor needs a value", idx); continue; }
mc_set_mode(idx, OpMode::PVT);
mc_set_target_torque(idx, static_cast<int16_t>(to_ll(vstr)));
RCLCPP_INFO(get_logger(), "[S%d] PVT tor=%s", idx, vstr.c_str());
continue;
}
// ======= CSP / CSV / CST 指令解析处理 =======
if (kind=="pos" || kind=="position") {
if (vstr.empty()) {
RCLCPP_WARN(get_logger(), "[S%d] pos needs a value; ignored", idx);
continue;
}
// switch (idx)
// {
// case 0:
// idx = 2;
// break;
// case 1:
// idx = 3;
// break;
// case 2:
// idx = 0;
// break;
// case 3:
// idx = 1;
// break;
// case 4:
// idx = 5;
// break;
// case 5:
// idx = 4;
// break;
// default:
// break;
// }
mc_set_mode(idx, OpMode::CSP);
mc_set_target_position(idx, static_cast<int32_t>(to_ll(vstr)));
// RCLCPP_INFO(get_logger(), "[S%d] CSP pos=%s", idx, vstr.c_str());
} else if (kind=="vel" || kind=="velocity" || kind=="speed") {
if (vstr.empty()) {
RCLCPP_WARN(get_logger(), "[S%d] vel needs a value; ignored", idx);
continue;
}
mc_set_mode(idx, OpMode::CSV);
mc_set_target_velocity(idx, static_cast<int32_t>(to_ll(vstr)));
RCLCPP_INFO(get_logger(), "[S%d] CSV vel=%s", idx, vstr.c_str());
} else if (kind=="tor" || kind=="torque") {
if (vstr.empty()) {
RCLCPP_WARN(get_logger(), "[S%d] tor needs a value; ignored", idx);
continue;
}
mc_set_mode(idx, OpMode::CST);
mc_set_target_torque(idx, static_cast<int16_t>(to_ll(vstr)));
RCLCPP_INFO(get_logger(), "[S%d] CST tor=%s", idx, vstr.c_str());
} else if (kind=="ena" || kind=="enable") {
if (vstr.empty()) {
RCLCPP_WARN(get_logger(), "[S%d] tor needs a value; ignored", idx);
continue;
}
mc_enable_run(idx, to_ll(vstr));
RCLCPP_INFO(get_logger(), "[S%d] enable=%s", idx, vstr.c_str());
}else {
RCLCPP_WARN(get_logger(), "[set] unknown field: '%s'", kind.c_str());
}
}
}
// 计时:以节点启动时为零,使用稳态时钟
rclcpp::Clock steady_clock_{RCL_STEADY_TIME};
rclcpp::Time start_time_steady_{0, 0, RCL_STEADY_TIME};
// 关节状态发布(实际值和命令值)
void publishJointState() {
// 实际值
sensor_msgs::msg::JointState js;
js.header.stamp = now();
js.name = joint_names_;
js.position.resize(num_joints_);
js.velocity.resize(num_joints_);
js.effort.resize(num_joints_);
for (int i = 0; i < num_joints_; ++i) {
uint16_t sw; int32_t pos, vel; int16_t tq;
mc_get_state(i, sw, pos, vel, tq);
js.position[i] = static_cast<double>(pos);
js.velocity[i] = static_cast<double>(vel);
js.effort[i] = static_cast<double>(tq);
}
js_pub_->publish(js);
// 命令位置CSP 回显),与实际值用同一时间戳
sensor_msgs::msg::JointState js_cmd;
js_cmd.header.stamp = js.header.stamp;
js_cmd.name = joint_names_;
js_cmd.position.resize(num_joints_);
for (int i = 0; i < num_joints_; ++i) {
const int32_t pc = g_state.pos_cmd[i].load(std::memory_order_relaxed); // 读取csp值
js_cmd.position[i] = static_cast<double>(pc);
}
js_cmd_pub_->publish(js_cmd);
// 计算相对启动的时间ms并量化到 0.1 ms
const rclcpp::Duration dt = steady_clock_.now() - start_time_steady_;
double t_ms = dt.seconds() * 1000.0;
t_ms = std::round(t_ms * 10.0) / 10.0; // 四舍五入到 0.1ms
// 生成“表格行”timestamp,轴1csp指令值,轴1真实位置,误差,轴2csp指令值,轴2真实位置,误差,...
std::ostringstream row;
row.setf(std::ios::fixed);
// 先写时间戳
row << std::setprecision(1) << t_ms;
if (num_joints_ > 0) row << ",";
for (int i = 0; i < num_joints_; ++i) {
const long long csp = static_cast<long long>(js_cmd.position[i]);
const long long act = static_cast<long long>(js.position[i]);
const long long err = csp - act;
row << csp << ',' << act << ',' << err;
if (i != num_joints_ - 1) row << ',';
}
const std::string line = row.str();
// 发布到 csp_table 话题
std_msgs::msg::String msg;
msg.data = line;
csp_table_pub_->publish(msg);
// 写入 CSV首行已写表头
// if (csv_.is_open()) {
// csv_ << line << '\n';
// if ((++csv_lines_ % 100) == 0) csv_.flush(); // 每100行刷一次盘减轻开销
// }
}
// 打开 CSV 并写表头
void open_csv_and_write_header() {
try {
std::filesystem::path p(csv_path_);
if (p.has_parent_path()) {
std::error_code ec;
std::filesystem::create_directories(p.parent_path(), ec);
if (ec) {
RCLCPP_WARN(get_logger(), "Failed to create dir for CSV: %s", ec.message().c_str());
}
}
csv_.open(csv_path_, std::ios::out | std::ios::trunc);
if (!csv_) {
RCLCPP_ERROR(get_logger(), "Cannot open CSV file: %s", csv_path_.c_str());
return;
}
// 表头轴1csp指令值,轴1真实位置,误差,轴2csp指令值,...
std::ostringstream header;
header << "time(ms)";
if (num_joints_ > 0) header << ","; // 后面还有列再加逗号
for (int i = 0; i < num_joints_; ++i) {
header << "axis" << (i+1) << "csp_command"
<< "," << "axis" << (i+1) << "real_position"
<< "," << "error";
if (i != num_joints_ - 1) header << ",";
}
csv_ << header.str() << '\n';
csv_.flush();
// 同步把表头也发到 topic
std_msgs::msg::String head_msg;
head_msg.data = header.str();
csp_table_pub_->publish(head_msg);
RCLCPP_INFO(get_logger(), "Logging CSP table to: %s", csv_path_.c_str());
} catch (const std::exception& e) {
RCLCPP_ERROR(get_logger(), "open_csv_and_write_header exception: %s", e.what());
}
}
int num_joints_{0};
std::vector<std::string> joint_names_;
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr set_sub_;
rclcpp::Publisher<sensor_msgs::msg::JointState>::SharedPtr js_pub_;
rclcpp::Publisher<sensor_msgs::msg::JointState>::SharedPtr js_cmd_pub_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr csp_table_pub_; // NEW
rclcpp::TimerBase::SharedPtr js_timer_;
rclcpp::TimerBase::SharedPtr watchdog_timer_;
rclcpp::Time last_command_time_;
// CSV
std::string csv_path_;
std::ofstream csv_;
size_t csv_lines_{0};
//看门狗
void watchdogCallback() {
const rclcpp::Time now = this->get_clock()->now();
const rclcpp::Duration dt = now - last_command_time_;
if (dt.seconds() > 0.2) {
//RCLCPP_WARN(get_logger(), "Command timeout! Stopping all motors."); //看门狗超时信息
for (int i = 0; i < num_joints_; ++i) {
mc_set_target_velocity(i, 0);
mc_set_target_torque(i, 0);
}
last_command_time_ = now;
}
}
};
int main(int argc, char** argv){
rclcpp::init(argc, argv);
// auto node = rclcpp::Node::make_shared("ethercat_node");
// int thread_priority = node->declare_parameter("thread_priority",50);
// pthread_t thread = pthread_self();
// struct sched_param param;
// param.sched_priority = thread_priority;
// int ret = pthread_setschedparam(thread,SCHED_FIFO,&param);
// if (ret != 0)
// {
// RCLCPP_ERROR(node->get_logger(),"Failed to set thread priority : %s", strerror(ret));
// }
// else
// {
// RCLCPP_INFO(node->get_logger(),"thread priority set to : %s", thread_priority);
// }
rclcpp::on_shutdown([]{
ethercat_core::stop_soft();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
ethercat_core::request_exit();
});
rclcpp::spin(std::make_shared<EthercatNode>());
rclcpp::shutdown();
return 0;
}

View File

@@ -0,0 +1,157 @@
#include <chrono>
#include <cmath>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include <algorithm>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;
class EthercatVelLoop : public rclcpp::Node
{
public:
EthercatVelLoop() : Node("ethercat_vel_loop")
{
// 参数
topic_ = declare_parameter<std::string>("topic", "/ethercat/set");
max_vel_ = declare_parameter<int>("max_vel", 30000);
// 兼容旧参数名 forward_duration_sec如果外部还在用
double forward_duration = declare_parameter<double>("forward_duration_sec", 3.0);
ramp_duration_ = declare_parameter<double>("ramp_duration_sec", forward_duration);
decel_duration_ = declare_parameter<double>("decel_duration_sec", 1.0);
stop_duration_ = declare_parameter<double>("stop_duration_sec", 1.0);
rate_hz_ = declare_parameter<double>("rate_hz", 10.0);
motor_ids_ = declare_parameter<std::vector<int64_t>>("motor_ids", {0, 1, 2});
if (rate_hz_ <= 0.0) {
RCLCPP_WARN(get_logger(), "rate_hz <= 0, force to 10 Hz");
rate_hz_ = 10.0;
}
pub_ = create_publisher<std_msgs::msg::String>(topic_, 10);
timer_period_ = std::chrono::duration<double>(1.0 / rate_hz_);
timer_ = create_wall_timer(
std::chrono::duration_cast<std::chrono::milliseconds>(timer_period_),
std::bind(&EthercatVelLoop::onTimer, this));
RCLCPP_INFO(get_logger(),
"Started ethercat_vel_loop: topic=%s, max_vel=%d, ramp=%.3fs, decel=%.3fs, stop=%.3fs, rate=%.1fHz",
topic_.c_str(), max_vel_, ramp_duration_, decel_duration_, stop_duration_, rate_hz_);
}
private:
enum class Phase { FWD_ACCEL, FWD_DECEL, STOP1, REV_ACCEL, REV_DECEL, STOP2 };
void onTimer()
{
time_in_phase_ += std::chrono::duration<double>(timer_period_).count();
int target_vel = 0;
switch (phase_) {
case Phase::FWD_ACCEL: {
// 0 -> +max_vel_线性加速持续 ramp_duration_
double frac = ramp_duration_ > 0.0 ? std::min(1.0, time_in_phase_ / ramp_duration_) : 1.0;
target_vel = static_cast<int>(std::round(max_vel_ * frac));
if (time_in_phase_ >= ramp_duration_) {
// 加速完成后:如果需要减速缓冲,进入减速段,否则直接停
if (decel_duration_ > 0.0) switchPhase(Phase::FWD_DECEL);
else switchPhase(Phase::STOP1);
}
break;
}
case Phase::FWD_DECEL: {
// +max_vel_ -> 0线性减速持续 decel_duration_
double frac = decel_duration_ > 0.0 ? std::min(1.0, time_in_phase_ / decel_duration_) : 1.0;
target_vel = static_cast<int>(std::round(max_vel_ * (1.0 - frac)));
if (time_in_phase_ >= decel_duration_) {
switchPhase(Phase::STOP1);
}
break;
}
case Phase::STOP1: {
target_vel = 0;
if (time_in_phase_ >= stop_duration_) {
switchPhase(Phase::REV_ACCEL);
}
break;
}
case Phase::REV_ACCEL: {
// 0 -> -max_vel_线性加速持续 ramp_duration_
double frac = ramp_duration_ > 0.0 ? std::min(1.0, time_in_phase_ / ramp_duration_) : 1.0;
target_vel = -static_cast<int>(std::round(max_vel_ * frac));
if (time_in_phase_ >= ramp_duration_) {
if (decel_duration_ > 0.0) switchPhase(Phase::REV_DECEL);
else switchPhase(Phase::STOP2);
}
break;
}
case Phase::REV_DECEL: {
// -max_vel_ -> 0线性减速持续 decel_duration_
double frac = decel_duration_ > 0.0 ? std::min(1.0, time_in_phase_ / decel_duration_) : 1.0;
target_vel = -static_cast<int>(std::round(max_vel_ * (1.0 - frac)));
if (time_in_phase_ >= decel_duration_) {
switchPhase(Phase::STOP2);
}
break;
}
case Phase::STOP2: {
target_vel = 0;
if (time_in_phase_ >= stop_duration_) {
switchPhase(Phase::FWD_ACCEL);
}
break;
}
}
// 拼装字符串:"0 vel XXX; 1 vel XXX; 2 vel XXX"
std::ostringstream oss;
for (size_t i = 0; i < motor_ids_.size(); ++i) {
if (i > 0) oss << "; ";
oss << motor_ids_[i] << " vel " << target_vel;
}
std_msgs::msg::String msg;
msg.data = oss.str();
pub_->publish(msg);
}
void switchPhase(Phase next)
{
phase_ = next;
time_in_phase_ = 0.0;
}
// 参数
std::string topic_;
int max_vel_;
double ramp_duration_;
double decel_duration_;
double stop_duration_;
double rate_hz_;
std::vector<int64_t> motor_ids_;
// 发布与定时
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr pub_;
rclcpp::TimerBase::SharedPtr timer_;
std::chrono::duration<double> timer_period_;
// 状态机
Phase phase_{Phase::FWD_ACCEL};
double time_in_phase_{0.0};
};
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<EthercatVelLoop>());
rclcpp::shutdown();
return 0;
}

View File

@@ -0,0 +1,147 @@
/**
* ROS2上位机和实时循环的通信桥梁
*
* 模块功能:
* - 读取实时循环状态 g_state
* - 给实时循环发送指令 g_cmd
*
* 线程模型与并发:
* - 多生产者ROS2 回调/控制线程) --> 写 g_cmd / 读 g_state
* - 单消费者1kHz 实时循环) --> 读 g_cmd / 写 g_state。
* - 全部原子变量使用 memory_order_relaxed低延迟、低开销
*
* 数据与单位约定(与从站字典保持一致):
* - 位置编码器计数counts
* - 速度counts/s
* - 扭矩:
* - 模式DS402 0x6060CSP=8, CSV=9, CST=10, PVT=5
* - PVT 增益:这里以 double 表示的无量纲系数
*
*/
#include "ethercat_control/motor_control.hpp"
MotorCommand g_cmd; //全局命令缓冲
MotorState g_state; //全局状态缓冲
static inline int clamp_index(int idx) { return std::clamp(idx, 0, NUM_SLAVES - 1); }
// 初始化所有轴的缺省命令与状态
void mc_init_defaults(OpMode default_mode) {
for (int i = 0; i < NUM_SLAVES; ++i) {
g_cmd.target_vel[i].store(0, std::memory_order_relaxed);
g_cmd.target_pos[i].store(0, std::memory_order_relaxed);
g_cmd.target_torque[i].store(0, std::memory_order_relaxed);
g_cmd.mode[i].store(static_cast<int8_t>(default_mode), std::memory_order_relaxed);
g_cmd.run_enable[i].store(false, std::memory_order_relaxed);
g_cmd.fault_reset[i].store(false, std::memory_order_relaxed);
g_cmd.quick_stop[i].store(false, std::memory_order_relaxed);
g_state.status_word[i].store(0, std::memory_order_relaxed);
g_state.pos_act[i].store(0, std::memory_order_relaxed);
g_state.vel_act[i].store(0, std::memory_order_relaxed);
g_state.torque_act[i].store(0, std::memory_order_relaxed);
g_state.pos_cmd[i].store(0, std::memory_order_relaxed);
// 新增PVT 缺省增益(可按需改)
g_cmd.pvt_kp[i].store(0.0, std::memory_order_relaxed);
g_cmd.pvt_kd[i].store(0.0, std::memory_order_relaxed);
}
}
//设置工作模式(轴号,操作模式)
void mc_set_mode(int idx, OpMode mode) {
if (!mc_valid_index(idx)) return;
g_cmd.mode[idx].store(static_cast<int8_t>(mode), std::memory_order_relaxed);
}
//设置速度(轴号,速度)
void mc_set_target_velocity(int idx, int32_t vel) {
if (!mc_valid_index(idx)) return;
g_cmd.target_vel[idx].store(vel, std::memory_order_relaxed);
}
//设置位置(轴号,位置)
void mc_set_target_position(int idx, int32_t pos) {
if (!mc_valid_index(idx)) return;
g_cmd.target_pos[idx].store(pos, std::memory_order_relaxed);
}
//设置扭矩(轴号,扭矩)
void mc_set_target_torque(int idx, int16_t tq) {
if (!mc_valid_index(idx)) return;
g_cmd.target_torque[idx].store(tq, std::memory_order_relaxed);
}
// 设置所有电机PVT kp kd
void mc_set_all_pvt_gains(double kp, double kd) {
for (int i = 0; i < NUM_SLAVES; ++i) {
g_cmd.pvt_kp[i].store(kp, std::memory_order_relaxed);
g_cmd.pvt_kd[i].store(kd, std::memory_order_relaxed);
}
}
// 设置PVT kp kd 轴号kpkd
void mc_set_pvt_gains(int axis, double kp, double kd) {
if (axis < 0 || axis >= NUM_SLAVES) return;
g_cmd.pvt_kp[axis].store(kp, std::memory_order_relaxed);
g_cmd.pvt_kd[axis].store(kd, std::memory_order_relaxed);
}
//使能/禁止运行(轴号)
void mc_enable_run(int idx, bool enable) {
if (!mc_valid_index(idx)) return;
g_cmd.run_enable[idx].store(enable, std::memory_order_relaxed);
}
//设置所有电机运行模式(模式)
void mc_set_all_mode(OpMode mode) {
for (int i = 0; i < NUM_SLAVES; ++i) mc_set_mode(i, mode);
}
//设置所有电机速度(速度)
void mc_set_all_target_velocity(int32_t vel) {
for (int i = 0; i < NUM_SLAVES; ++i) mc_set_target_velocity(i, vel);
}
//批量使能/禁止运行
void mc_enable_all(bool enable) {
for (int i = 0; i < NUM_SLAVES; ++i) mc_enable_run(i, enable);
}
//故障复位,待开发
void mc_fault_reset(int idx) {
if (!mc_valid_index(idx)) return;
g_cmd.fault_reset[idx].store(true, std::memory_order_relaxed);
}
//急停(轴号)
void mc_quick_stop(int idx) {
if (!mc_valid_index(idx)) return;
g_cmd.quick_stop[idx].store(true, std::memory_order_relaxed);
}
//读取轴状态(轴号,状态字,位置,速度,扭矩)
void mc_get_state(int idx, uint16_t &status, int32_t &pos, int32_t &vel, int16_t &torque) {
idx = clamp_index(idx);
// This should be done in HAL
// slave index transport into joint index.
// switch (idx)
// {
// case 0:
// idx = 2;
// break;
// case 1:
// idx = 3;
// break;
// case 2:
// idx = 0;
// break;
// case 3:
// idx = 1;
// break;
// case 4:
// idx = 5;
// break;
// case 5:
// idx = 4;
// break;
// default:
// break;
// }
status = g_state.status_word[idx].load(std::memory_order_relaxed);
pos = g_state.pos_act[idx].load(std::memory_order_relaxed);
vel = g_state.vel_act[idx].load(std::memory_order_relaxed);
torque = g_state.torque_act[idx].load(std::memory_order_relaxed);
}

View File

@@ -55,12 +55,13 @@ class JoyControlNode(Node):
self.get_logger().info('game pad node initialized')
def joy_callback(self, msg):
print("joy_callback: ")
# 处理按键
for i in range(len(msg.buttons)):
if msg.buttons[i] != self.previous_buttons[i]:
self.previous_buttons[i] = msg.buttons[i]
command = self.button_map[i]+","+str(msg.buttons[i])
self.get_logger().info("key:"+command+","+str(msg.buttons[i]))
self.get_logger().info("key:"+command)
self.command_publisher.publish(String(data=command))
#处理axis

View File

View File

@@ -0,0 +1,263 @@
from jodellSdk.jodellSdkDemo import RgClawControl
import time
import rclpy
from rclpy.node import Node
from rclpy.action import ActionServer
from std_msgs.msg import String
from interfaces.msg import GripperStatus
from rclpy.executors import MultiThreadedExecutor
from interfaces.action import GripperCmd
import sys
import os
import threading
class GripperDevNode(Node):
def __init__(self):
super().__init__('gripper_dev_node')
self.lock = threading.Lock()
self.declare_parameter('port','/dev/ttyUSB1')
param = self.get_parameter('port')
self.com_dev = param.value
self.declare_parameter('devid',6)
param = self.get_parameter('devid')
self.devid = param.value
self.declare_parameter('name','/gripper_cmd0')
param = self.get_parameter('name')
self.srv_name = param.value
self.declare_parameter('status_topic','gripper/status')
status_param = self.get_parameter('status_topic')
self.status_topic = status_param.value
self.get_logger().info(f'param: {self.com_dev} {self.devid} {self.srv_name}')
self.get_logger().info(f'ros param:{self.com_dev},{self.devid},{self.srv_name}')
self.action=ActionServer(self,GripperCmd,self.srv_name,self.gripper_cmd_callback_action)
self.clawControl = RgClawControl()
self.target_loc=0
self.target_torque=0
self.target_speed=0
self.cur_loc=-1
self.cur_torque=-1
self.cur_goal=None
self.timer_on=False
self.target_mode=0
self.enable_ok=False
self.state=''
self.status_pub = self.create_publisher(GripperStatus, self.status_topic, 10)
self.status_timer = self.create_timer(0.1, self.status_timer_callback)
with self.lock:
flag = self.clawControl.serialOperation(self.com_dev, 115200, True)
if flag==1:
ret = self.clawControl.enableClamp(self.devid, True)
if ret==1:
self.enable_ok=True
else:
self.get_logger().info(f'{self.com_dev} enable fail!')
self.get_logger().info(f'gripper_dev_node init: {self.com_dev}')
#self.feedback_timer = self.create_timer(0.1, self.update_feedback)
def status_timer_callback(self):
if not self.enable_ok:
return
# Query all status in one lock cycle to minimize context switching
with self.lock:
try:
loc = self.clawControl.getClampCurrentLocation(self.devid)
speed = self.clawControl.getClampCurrentSpeed(self.devid)
torque = self.clawControl.getClampCurrentTorque(self.devid)
state = self.clawControl.getClampCurrentState(self.devid)
except Exception as e:
self.get_logger().error(f"Serial read error: {e}")
return
if loc and speed and torque and state:
self.cur_loc = loc[0]
self.cur_speed = speed[0]
self.cur_torque = torque[0]
self.state = state[0]
msg = GripperStatus()
msg.header.stamp = self.get_clock().now().to_msg()
msg.loc = float(self.cur_loc)
msg.speed = float(self.cur_speed)
msg.torque = float(self.cur_torque)
msg.state = self.state
self.status_pub.publish(msg)
def gripper_cmd_callback_action(self,goal):
self.cur_goal=goal
req=goal.request
self.target_loc=req.loc
self.target_speed=req.speed
self.target_torque=req.torque
self.target_mode=req.mode
self.get_logger().info(f'\n----------\nrecv goal {self.devid} {self.target_loc} {self.target_speed} {self.target_torque} {self.target_mode}')
if self.enable_ok:
# Use local copies of latest status to avoid blocking the serial port again
self.get_logger().info(f"origin state: {self.state}")
self.get_logger().info(f"origin loc: {self.cur_loc} -> {self.target_loc}")
self.get_logger().info(f"origin torque: {self.cur_torque} -> {self.target_torque}")
if (self.target_mode==0 or self.target_mode==1 or self.target_mode==2) : # loc/torque/both mode
if (self.target_torque == 0 or self.target_loc == 0) : # open gripper
if (self.cur_loc == 0 and self.state == "手指已到达指定的位置,没有检测到物体") :
result=GripperCmd.Result()
result.state=self.state
result.result=1
goal.succeed()
self.get_logger().info(f'result: {result}\n')
return result
else: # close gripper
if (self.cur_loc > 0 and self.state == "手指在闭合检测到物体") :
result=GripperCmd.Result()
result.state=self.state
result.result=1
goal.succeed()
self.get_logger().info(f'result: {result}\n')
return result
else:
result=GripperCmd.Result()
result.state="mode error!"
result.result=0
goal.abort()
self.get_logger().info(f'result: {result}\n')
return result
# Retry sending command up to 3 times to ensure stability
max_retries = 3
cmd_sent = False
for i in range(max_retries):
with self.lock:
try:
self.clawControl.runWithParam(self.devid, self.target_loc, self.target_speed, self.target_torque)
time.sleep(0.05)
# Force update status immediately after command
loc = self.clawControl.getClampCurrentLocation(self.devid)
speed = self.clawControl.getClampCurrentSpeed(self.devid)
torque = self.clawControl.getClampCurrentTorque(self.devid)
state = self.clawControl.getClampCurrentState(self.devid)
if loc and speed and torque and state:
self.cur_loc = loc[0]
self.cur_speed = speed[0]
self.cur_torque = torque[0]
self.state = state[0]
cmd_sent = True
break
except Exception as e:
self.get_logger().warn(f"Failed to send command (attempt {i+1}): {e}")
time.sleep(0.05)
if not cmd_sent:
result=GripperCmd.Result()
result.state="Serial command failed after retries"
result.result=0
goal.abort()
return result
loop_flag=True
loop_times=0
while loop_flag:
# Use cached values from the periodic status timer to reduce serial traffic and collisions
# The timer runs at 10Hz, which is frequent enough for the action monitor
self.get_logger().info(f"mode: {self.target_mode}")
self.get_logger().info(f"torque: {self.cur_torque} {self.target_torque}")
self.get_logger().info(f"loc: {self.cur_loc} {self.target_loc}")
self.get_logger().info(f"state: {self.state}")
if (self.cur_loc == 0 and (self.state == "手指已到达指定的位置,没有检测到物体" or self.state == "手指在张开检测到物体")) : #手指张开到位
result=GripperCmd.Result()
result.state=self.state
result.result=1
goal.succeed()
self.get_logger().info(f'result: {result}\n')
return result
elif (self.cur_loc > 0 and self.state == "手指在闭合检测到物体") : #手指闭合,并检测到物体
result=GripperCmd.Result()
result.state=self.state
result.result=1
goal.succeed()
self.get_logger().info(f'result: {result}\n')
return result
elif (self.cur_loc > 0 and self.cur_loc == self.target_loc and self.state == "手指已到达指定的位置,没有检测到物体"): #手指闭合,未检测到物体
result=GripperCmd.Result()
result.state=self.state
result.result=0
goal.abort()
self.get_logger().info(f'result: {result}\n')
return result
else:
pass
feedback_msg = GripperCmd.Feedback()
feedback_msg.loc=int(self.cur_loc)
feedback_msg.speed=int(self.cur_speed)
feedback_msg.torque=int(self.cur_torque)
try:
# Check if the interface supports state in feedback
feedback_msg.state=self.state
except AttributeError:
pass
self.get_logger().info(f'update feedback: {feedback_msg}')
goal.publish_feedback(feedback_msg)
time.sleep(1) # Monitor every 1 second
loop_times+=1
if loop_times>20: # Increased timeout since we are less invasive now
result=GripperCmd.Result()
result.state="loop timeout!!!"
result.result=0
goal.abort()
loop_times=0
self.get_logger().info(f'result: {result}\n')
return result
else:
result=GripperCmd.Result()
result.state="uart open fail & enable fail!"
result.result=0
goal.abort()
self.get_logger().info(f'result: {result}\n')
return result
self.timer_on=True
self.get_logger().info(f'location diff: {self.cur_loc} {self.target_loc}')
state = self.clawControl.getClampCurrentState(self.devid)
result=GripperCmd.Result()
result.state=state[0]
result.result=1
goal.succeed()
self.get_logger().info(f'action finish {state}\n**********\n')
return result
def destroy_node(self):
super().destroy_node()
with self.lock:
flag = self.clawControl.serialOperation(self.com_dev, 115200, False)
def main():
rclpy.init()
node=GripperDevNode()
# Using more threads to ensure the timer and multiple action/service requests don't block each other
executor = MultiThreadedExecutor(num_threads=10)
executor.add_node(node)
try:
executor.spin()
finally:
node.destroy_node()
rclpy.shutdown()
pass
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,11 @@
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(package="gripper_dev",executable="gripper_dev_node",name="gripper0_dev_node",
parameters=[{'port':'/dev/robot/gripper0'}, {'name':'/gripper_cmd0'}, {'devid':9},
{'status_topic':'/gripper0/status'}]),
Node(package="gripper_dev",executable="gripper_dev_node",name="gripper1_dev_node",
parameters=[{'port':'/dev/robot/gripper1'}, {'name':'/gripper_cmd1'}, {'devid':6},
{'status_topic':'/gripper1/status'}])
])

26
gripper_dev/package.xml Normal file
View File

@@ -0,0 +1,26 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>gripper_dev</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="you@example.com">h</maintainer>
<license>TODO: License declaration</license>
<buildtool_depend>ament_python</buildtool_depend>
<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
<depend>std_msgs</depend>
<depend>sensor_msgs</depend>
<depend>rclcpp</depend>
<depend>rclpy</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_python</build_type>
</export>
</package>

Binary file not shown.

View File

4
gripper_dev/setup.cfg Normal file
View File

@@ -0,0 +1,4 @@
[develop]
script_dir=$base/lib/gripper_dev
[install]
install_scripts=$base/lib/gripper_dev

24
gripper_dev/setup.py Normal file
View File

@@ -0,0 +1,24 @@
from setuptools import setup
import os
from glob import glob
package_name='gripper_dev'
setup(
name='gripper_dev',
version='0.1',
description='A simple package for gripper_dev',
author='Gripper Dev',
author_email='gripper_dev@example.com',
url='https://github.com/gripper_dev',
packages=['gripper_dev'],
data_files=[
('share/ament_index/resource_index/packages', ['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
(os.path.join('share', package_name, 'launch'), glob('launch/*.launch.py')),
],
install_requires=[
'setuptools',
],
entry_points={
'console_scripts':['gripper_dev_node = gripper_dev.gripper_dev_node:main']
}
)

View File

@@ -12,11 +12,12 @@ find_package(rclcpp REQUIRED)
find_package(rosidl_default_generators REQUIRED)
find_package(message_filters REQUIRED)
find_package(realsense2_camera_msgs REQUIRED)
find_package(interfaces REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/ImgMsg.msg"
DEPENDENCIES sensor_msgs
)
#rosidl_generate_interfaces(${PROJECT_NAME}
# "msg/ImgMsg.msg"
# DEPENDENCIES sensor_msgs
#)
include_directories(
include
${CMAKE_CURRENT_BINARY_DIR}
@@ -24,18 +25,16 @@ include_directories(
)
add_executable(img_dev_node src/main.cpp src/img_dev_node.cpp)
add_dependencies(img_dev_node ${PROJECT_NAME}__rosidl_typesupport_cpp)
#add_dependencies(img_dev_node ${PROJECT_NAME}__rosidl_typesupport_cpp)
target_link_libraries(img_dev_node
${PROJECT_NAME}__rosidl_typesupport_cpp
${PROJECT_NAME}__rosidl_typesupport_c
message_filters::message_filters
)
ament_target_dependencies(img_dev_node rclcpp sensor_msgs realsense2_camera_msgs)
ament_target_dependencies(img_dev_node rclcpp sensor_msgs realsense2_camera_msgs interfaces)
install(TARGETS img_dev_node DESTINATION lib/${PROJECT_NAME})
install(DIRECTORY msg/ DESTINATION share/${PROJECT_NAME})
# install(DIRECTORY msg/ DESTINATION share/${PROJECT_NAME})
install(DIRECTORY launch/ DESTINATION share/${PROJECT_NAME}/launch})
ament_export_dependencies(rosidl_default_runtime)

View File

@@ -5,7 +5,7 @@
#include "message_filters/subscriber.h"
#include "message_filters/time_synchronizer.h"
#include "message_filters/sync_policies/approximate_time.hpp"
#include "img_dev/msg/img_msg.hpp"
#include "interfaces/msg/img_msg.hpp"
using namespace std;
namespace img_dev{
@@ -14,7 +14,7 @@ namespace img_dev{
ImageSubscriber() ;
void pub_msg(const sensor_msgs::msg::Image& c_img,const sensor_msgs::msg::Image& d_img,const ImgCfg& cfg);
private:
rclcpp::Publisher<img_dev::msg::ImgMsg>::SharedPtr img_pub;
std::shared_ptr<img_dev::msg::ImgMsg> img_msg;
rclcpp::Publisher<interfaces::msg::ImgMsg>::SharedPtr img_pub;
std::shared_ptr<interfaces::msg::ImgMsg> img_msg;
};
}

View File

@@ -7,32 +7,15 @@ from launch.conditions import IfCondition
def generate_launch_description():
return LaunchDescription([
# 启动orbbec_camera
ExecuteProcess(
cmd=[
'ros2',
'launch',
'orbbec_camera',
'multi_camera.launch.py'
],
output='screen'
),
ExecuteProcess(
cmd=[
'ros2',
'launch',
'realsense2_camera',
'rs_launch.py'
],
output='screen'
),
#ExecuteProcess(cmd=['ros2','launch', 'orbbec_camera', 'multi_camera.launch.py'],output='screen'),
ExecuteProcess(cmd=['ros2','launch', 'orbbec_camera', 'gemini_330_series.launch.py'],output='screen'),
ExecuteProcess(cmd=['sleep', '2'],output='screen'),
# ExecuteProcess(cmd=['ros2','launch', 'realsense2_camera','rs_launch.py'],output='screen'),
ExecuteProcess(cmd=['ros2', 'launch', 'realsense2_camera', 'rs_multi_camera_launch.py', 'serial_no1:=\'405622072723\'', 'serial_no2:=\'247122074159\''], output='screen'),
ExecuteProcess(cmd=['sleep', '2'],output='screen'),
# 等待一段时间确保相机启动完成
ExecuteProcess(
cmd=['sleep', '3'],
output='screen'
),
# 启动img_dev_node
Node(

View File

@@ -1,7 +0,0 @@
sensor_msgs/Image image_depth
sensor_msgs/Image image_color
float64[] karr
float64[] darr
string source
string position
string type

View File

@@ -1,6 +1,6 @@
#include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/image.hpp>
#include "img_dev/msg/img_msg.hpp"// 自定义消息头文件
#include "interfaces/msg/img_msg.hpp"// 自定义消息头文件
#include "img_dev/img_cfg.hpp"
#include <message_filters/subscriber.h>
#include <message_filters/sync_policies/approximate_time.hpp>
@@ -8,8 +8,8 @@
#include"img_dev/img_dev_node.hpp"
namespace img_dev{
ImageSubscriber::ImageSubscriber() : Node("image_subscriber") {
img_msg= std::make_shared<img_dev::msg::ImgMsg>();
img_pub = this->create_publisher<img_dev::msg::ImgMsg>("/img_msg", 10);
img_msg= std::make_shared<interfaces::msg::ImgMsg>();
img_pub = this->create_publisher<interfaces::msg::ImgMsg>("/img_msg", 10);
}
static long long pub_cnt=0;
void ImageSubscriber::pub_msg(const sensor_msgs::msg::Image& c_img,const sensor_msgs::msg::Image& d_img,const ImgCfg& cfg){
@@ -21,8 +21,9 @@ void ImageSubscriber::pub_msg(const sensor_msgs::msg::Image& c_img,const sensor_
img_msg->karr=cfg.k_arr;
img_msg->darr=cfg.d_arr;
img_pub->publish(*img_msg);
pub_cnt+=1;
std::cout<<"pub msg["<<pub_cnt<<"]:"<<img_msg->darr[0]<<","<<img_msg->darr[1]<<",k:"<<img_msg->karr[0]<<","<<img_msg->karr[1]<<std::endl;
// pub_cnt+=1;
// std::cout<<"pub msg["<<pub_cnt<<"]:"<<img_msg->image_color.width<<"x"<<img_msg->image_color.height<<";"<<img_msg->darr[0]<<","<<img_msg->darr[1]<<",k:"<<img_msg->karr[0]<<","<<img_msg->karr[1]<<std::endl;
}
}

View File

@@ -1,7 +1,7 @@
#include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/image.hpp>
#include <sensor_msgs/msg/camera_info.hpp>
#include "img_dev/msg/img_msg.hpp"
#include "interfaces/msg/img_msg.hpp"
#include "img_dev/img_cfg.hpp"
#include <message_filters/subscriber.h>
#include <message_filters/sync_policies/approximate_time.hpp>
@@ -15,10 +15,11 @@ using namespace std;
using namespace img_dev;
using SyncPolicy=message_filters::sync_policies::ApproximateTime<sensor_msgs::msg::Image,sensor_msgs::msg::Image>;
static shared_ptr<ImageSubscriber> cur_node=nullptr;
ImgCfg cfg0=ImgCfg("orbbec", "myType","left","/camera_01/color/image_raw","/camera_01/depth/image_raw");
ImgCfg cfg1=ImgCfg("orbbec", "myType","right","/camera_02/color/image_raw","/camera_02/depth/image_raw");
ImgCfg cfg0=ImgCfg("orbbec", "myType","top","/camera/color/image_raw","/camera/depth/image_raw");
ImgCfg cfg1=ImgCfg("orbbec", "myType","null","/camera_02/color/image_raw","/camera_02/depth/image_raw");
//
ImgCfg cfg2=ImgCfg("realsense", "myType","front","/camera/color/image_raw","/camera/depth/image_raw");
ImgCfg cfg2=ImgCfg("realsense", "myType","left","/camera/camera/rgbd","/camera/camera/rgbd");
ImgCfg cfg3=ImgCfg("realsense", "myType","right","/camera2/camera2/rgbd","/camera2/camera2/rgbd");
void sync_cb0(const sensor_msgs::msg::Image& c_img, const sensor_msgs::msg::Image& d_img) {
cur_node->pub_msg(c_img,d_img,cfg0);
}
@@ -26,14 +27,17 @@ void sync_cb1(const sensor_msgs::msg::Image& c_img, const sensor_msgs::msg::Imag
cur_node->pub_msg(c_img,d_img,cfg1);
}
int main(int argc, char* argv[]) {
setenv("ROS_DISABLE_SHM", "1", 1); //禁用共享内存
rclcpp::init(argc, argv);
cur_node=make_shared<ImageSubscriber>();
if(!cur_node){
RCLCPP_ERROR(rclcpp::get_logger("main"),"img_dev node create error!!!!");
return -1;
}
#if 1
//////////////////奥比中光1 START///////////////////////
rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr camera01_info=cur_node->create_subscription<sensor_msgs::msg::CameraInfo>("/camera_01/color/camera_info", 10,
rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr camera01_info=cur_node->create_subscription<sensor_msgs::msg::CameraInfo>("/camera/color/camera_info", 10,
[&](const sensor_msgs::msg::CameraInfo& info){
if(info.k.empty()||info.d.empty())
return;
@@ -52,7 +56,8 @@ int main(int argc, char* argv[]) {
auto sync0 = make_shared<message_filters::Synchronizer<SyncPolicy>>(SyncPolicy(10), c_sub0, d_sub0);
sync0->registerCallback(&sync_cb0);
//////////////////奥比中光1 END///////////////////////
#endif
#if 0
//////////////////奥比中光2 START///////////////////////
rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr camera02_info=cur_node->create_subscription<sensor_msgs::msg::CameraInfo>("/camera_02/color/camera_info", 10,
@@ -73,9 +78,9 @@ int main(int argc, char* argv[]) {
auto sync1 = make_shared<message_filters::Synchronizer<SyncPolicy>>(SyncPolicy(10), c_sub1, d_sub1);
sync1->registerCallback(&sync_cb1);
//////////////////奥比中光2 END///////////////////////
#endif
//////////////////realsense START///////////////////////
rclcpp::Subscription<realsense2_camera_msgs::msg::RGBD>::SharedPtr rgbd_sub_ = cur_node->create_subscription<realsense2_camera_msgs::msg::RGBD>("/camera/camera/rgbd", 10, [&](const realsense2_camera_msgs::msg::RGBD& msg) {
rclcpp::Subscription<realsense2_camera_msgs::msg::RGBD>::SharedPtr rgbd1_sub_ = cur_node->create_subscription<realsense2_camera_msgs::msg::RGBD>("/camera1/camera1/rgbd", 10, [&](const realsense2_camera_msgs::msg::RGBD& msg) {
if(cfg2.k_arr.size()==0||cfg2.d_arr.size()==0){
cfg2.k_arr.assign(msg.rgb_camera_info.k.begin(), msg.rgb_camera_info.k.end());
cfg2.d_arr.assign(msg.rgb_camera_info.d.begin(), msg.rgb_camera_info.d.end());
@@ -85,6 +90,23 @@ int main(int argc, char* argv[]) {
cur_node->pub_msg(msg.rgb,msg.depth,cfg2);
});
//////////////////realsense END///////////////////////
#if 1
//////////////////realsense START///////////////////////
rclcpp::Subscription<realsense2_camera_msgs::msg::RGBD>::SharedPtr rgbd2_sub_ = cur_node->create_subscription<realsense2_camera_msgs::msg::RGBD>("/camera2/camera2/rgbd", 10, [&](const realsense2_camera_msgs::msg::RGBD& msg) {
if(cfg3.k_arr.size()==0||cfg3.d_arr.size()==0){
cfg3.k_arr.assign(msg.rgb_camera_info.k.begin(), msg.rgb_camera_info.k.end());
cfg3.d_arr.assign(msg.rgb_camera_info.d.begin(), msg.rgb_camera_info.d.end());
cfg3.arr_copy+=1;
cout<<"copy:"<<"k:"<<cfg3.k_arr[0]<<"d:"<<cfg3.d_arr[0]<<endl;
}
cur_node->pub_msg(msg.rgb,msg.depth,cfg3);
});
//////////////////realsense END///////////////////////
#endif
rclcpp::spin(cur_node);
rclcpp::shutdown();
return 0;

View File

@@ -10,20 +10,21 @@ find_package(ament_cmake REQUIRED)
find_package(std_msgs REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rosidl_default_generators REQUIRED)
find_package(interfaces REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME} "msg/ImuMsg.msg" DEPENDENCIES sensor_msgs)
####rosidl_generate_interfaces(${PROJECT_NAME} "msg/ImuMsg.msg" DEPENDENCIES sensor_msgs)
include_directories(include ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/rosidl_generator_cpp)
add_executable(imu_dev_node src/main.cpp)
ament_target_dependencies(imu_dev_node rclcpp std_msgs sensor_msgs)
add_dependencies(imu_dev_node ${PROJECT_NAME}__rosidl_typesupport_cpp)
target_link_libraries(imu_dev_node
${PROJECT_NAME}__rosidl_typesupport_cpp
${PROJECT_NAME}__rosidl_typesupport_c
)
ament_target_dependencies(imu_dev_node rclcpp std_msgs sensor_msgs interfaces)
###add_dependencies(imu_dev_node ${PROJECT_NAME}__rosidl_typesupport_cpp)
#target_link_libraries(imu_dev_node
# ${PROJECT_NAME}__rosidl_typesupport_cpp
# ${PROJECT_NAME}__rosidl_typesupport_c
#)
install(TARGETS imu_dev_node DESTINATION lib/${PROJECT_NAME})
install(DIRECTORY launch DESTINATION share/${PROJECT_NAME}/launch)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights

View File

@@ -0,0 +1,39 @@
from launch import LaunchDescription
from launch_ros.actions import Node
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.substitutions import FindPackageShare
from launch.substitutions import PathJoinSubstitution
def generate_launch_description():
imu_node = Node(
package='imu',
executable='imu_node',
name='imu_node',
parameters=[{
'port_name': '/dev/ttyUSB1',
}]
)
imu_dev_node = Node(
package='imu_dev',
executable='imu_dev_node',
name='imu_dev_node',
output='screen'
)
openzen_dev = FindPackageShare(package="openzen_driver")
openzen_dev_launch_path = PathJoinSubstitution(
[openzen_dev, "launch", "openzen_lpms.launch.py"]
)
include_openzen_dev = IncludeLaunchDescription(
PythonLaunchDescriptionSource(openzen_dev_launch_path),
)
return LaunchDescription([
#imu_node,
#imu_dev_node,
include_openzen_dev
])

View File

@@ -1,4 +0,0 @@
string source
string type
string position
sensor_msgs/Imu imu

View File

@@ -1,23 +1,93 @@
#include "rclcpp/rclcpp.hpp"
#include "imu_dev/msg/imu_msg.hpp"
#include "interfaces/msg/imu_msg.hpp"
#include "sensor_msgs/msg/imu.hpp"
#include <fstream>
using namespace std;
void QuaternionToRPYRad(double qw, double qx, double qy, double qz, double &roll, double &pitch, double &yaw)
{
double norm = sqrt(qw*qw + qx*qx + qy*qy + qz*qz);
if (norm < 1e-12) { // 避免除以零
roll = 0;
pitch = 0;
yaw = 0;
return;
}
qw /= norm;
qx /= norm;
qy /= norm;
qz /= norm;
// 计算Roll角X轴
roll = atan2(2 * (qw*qx + qy*qz), 1 - 2 * (qx*qx + qy*qy));
// 计算Pitch角Y轴限制输入范围避免数值误差asin输入需在[-1,1]
double sin_pitch = 2 * (qw*qy - qz*qx);
sin_pitch = std::max(std::min(sin_pitch, 1.0), -1.0);
pitch = asin(sin_pitch);
// 计算Yaw角Z轴
yaw = atan2(2 * (qw*qz + qx*qy), 1 - 2 * (qy*qy + qz*qz));
}
// std::ofstream rpy_file("rpy.txt");
void QuaternionToRPYDeg(double qw, double qx, double qy, double qz, double &roll, double &pitch, double &yaw)
{
QuaternionToRPYRad(qw, qx, qy, qz, roll, pitch, yaw);
roll = roll * 180.0 / 3.1415926;
pitch = pitch * 180.0 / 3.1415926;
yaw = yaw * 180.0 / 3.1415926;
// rpy_file<<roll<<","<<pitch<<","<<yaw<<std::endl;
// std::cout << "Roll: " << roll << ", Pitch: " << pitch << ", Yaw: " << yaw << std::endl;
}
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
auto node = rclcpp::Node::make_shared("imu_dev_node");
auto imu_pub = node->create_publisher<imu_dev::msg::ImuMsg>("/imu_msg", 10);
auto imu_pub = node->create_publisher<interfaces::msg::ImuMsg>("/imu_msg", 10);
auto imu_sub=node->create_subscription<sensor_msgs::msg::Imu>("/imu/data", 10, [=](const sensor_msgs::msg::Imu::SharedPtr msg){
//cout<<msg->linear_acceleration.x<<","<<msg->linear_acceleration.y<<","<<msg->linear_acceleration.z<<endl;
//cout<<msg->angular_velocity.x<<","<<msg->angular_velocity.y<<","<<msg->angular_velocity.z<<endl;
//cout<<msg->orientation.x<<","<<msg->orientation.y<<","<<msg->orientation.z<<","<<msg->orientation.w<<endl;
imu_dev::msg::ImuMsg imu_msg;
interfaces::msg::ImuMsg imu_msg;
imu_msg.source="waveshare";
imu_msg.position="front";
imu_msg.type="None";
imu_msg.imu=*msg;
double gyroValues_[3];
QuaternionToRPYDeg(
imu_msg.imu.orientation.w,
imu_msg.imu.orientation.x,
imu_msg.imu.orientation.y,
imu_msg.imu.orientation.z,
gyroValues_[0],
gyroValues_[1],
gyroValues_[2]
);
imu_pub->publish(imu_msg);
});
///livox/imu
auto imu_sub_1=node->create_subscription<sensor_msgs::msg::Imu>("/livox/imu",10,[=](const sensor_msgs::msg::Imu::SharedPtr msg){
interfaces::msg::ImuMsg imu_msg;
imu_msg.source="livox";
imu_msg.position="front";
imu_msg.type="None";
imu_msg.imu=*msg;
imu_pub->publish(imu_msg);
});
rclcpp::spin(node);
rclcpp::shutdown();
}
// rpy_file.close();
}

View File

@@ -12,6 +12,7 @@ find_package(std_msgs REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)
find_package(message_filters REQUIRED)
find_package(interfaces REQUIRED)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
@@ -23,10 +24,10 @@ if(BUILD_TESTING)
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/InputMsg.msg"
DEPENDENCIES sensor_msgs
)
#rosidl_generate_interfaces(${PROJECT_NAME}
# "msg/InputMsg.msg"
# DEPENDENCIES sensor_msgs
#)
include_directories(
include
${CMAKE_CURRENT_BINARY_DIR}
@@ -34,17 +35,15 @@ include_directories(
)
add_executable(input_dev_node src/main.cpp src/input_dev_node.cpp src/key_map.cpp)
add_dependencies(input_dev_node ${PROJECT_NAME}__rosidl_typesupport_cpp)
#add_dependencies(input_dev_node ${PROJECT_NAME}__rosidl_typesupport_cpp)
target_link_libraries(input_dev_node
${PROJECT_NAME}__rosidl_typesupport_cpp
${PROJECT_NAME}__rosidl_typesupport_c
message_filters::message_filters
)
ament_target_dependencies(input_dev_node rclcpp sensor_msgs)
ament_target_dependencies(input_dev_node rclcpp sensor_msgs interfaces)
install(TARGETS input_dev_node DESTINATION lib/${PROJECT_NAME})
install(DIRECTORY msg/ DESTINATION share/${PROJECT_NAME})
install(DIRECTORY launch/ DESTINATION share/${PROJECT_NAME}/launch})
#install(DIRECTORY msg/ DESTINATION share/${PROJECT_NAME})
install(DIRECTORY launch/ DESTINATION share/${PROJECT_NAME}/launch)
ament_package()

View File

@@ -4,7 +4,7 @@
#include "message_filters/subscriber.h"
#include "message_filters/time_synchronizer.h"
#include "message_filters/sync_policies/approximate_time.hpp"
#include "input_dev/msg/input_msg.hpp"
#include "interfaces/msg/input_msg.hpp"
using namespace std;
namespace input_dev{
@@ -15,8 +15,8 @@ namespace input_dev{
void pub_msg(const int key,const InputCfg& cfg);
void pub_msg(const int key,const float val,const InputCfg& cfg);
private:
rclcpp::Publisher<input_dev::msg::InputMsg>::SharedPtr input_pub;
std::shared_ptr<input_dev::msg::InputMsg> input_msg;
rclcpp::Publisher<interfaces::msg::InputMsg>::SharedPtr input_pub;
std::shared_ptr<interfaces::msg::InputMsg> input_msg;
};
}

View File

@@ -12,7 +12,7 @@ def generate_launch_description():
'ros2',
'launch',
'game_pad',
'gampe_pad.launch.py'
'game_pad.launch.py'
],
output='screen'
),

View File

@@ -1,5 +0,0 @@
string source
string type
string position
int32 code
float32 value

View File

@@ -1,5 +1,5 @@
#include <rclcpp/rclcpp.hpp>
#include "input_dev/msg/input_msg.hpp"// 自定义消息头文件
#include "interfaces/msg/input_msg.hpp"// 自定义消息头文件
#include "input_dev/input_cfg.hpp"
#include <message_filters/subscriber.h>
#include <message_filters/sync_policies/approximate_time.hpp>
@@ -7,8 +7,8 @@
#include"input_dev/input_dev_node.hpp"
namespace input_dev{
InputSub::InputSub() : Node("InputSub") {
input_msg= std::make_shared<input_dev::msg::InputMsg>();
input_pub = this->create_publisher<input_dev::msg::InputMsg>("/input_msg", 10);
input_msg= std::make_shared<interfaces::msg::InputMsg>();
input_pub = this->create_publisher<interfaces::msg::InputMsg>("/input_msg", 10);
}
static long long pub_cnt=0;
void InputSub::pub_msg(const int key,const float val,const InputCfg& cfg){

Some files were not shown because too many files have changed in this diff Show More