AimRT 开发完整指南
基于
picture_service项目实战经验总结项目路径:
~/aimrt_picture_service
一、整体架构
┌─────────────────────────────────────────────────┐
│ interface 仓库 │
│ 定义 .srv / .msg 接口文件 │
│ ros2/robot/interaction/srv/GetPictureList.srv │
│ ros2/robot/interaction/srv/GetPicture.srv │
│ ros2/robot/interaction/msg/PictureInfo.msg │
│ ros2/robot/common/CommonRequest.msg │
│ ros2/robot/common/CommonResponse.msg │
└──────────────────┬──────────────────────────────┘
│ 编译
▼
┌─────────────────────────────────────────────────┐
│ aimdk_msgs 包 │
│ 自动生成 C++ 头文件 │
│ aimdk_msgs/srv/GetPictureList.aimrt_rpc.srv.h │
│ aimdk_msgs/srv/GetPicture.aimrt_rpc.srv.h │
│ 包含 xxxSyncService、xxx_Request、xxx_Response │
└──────────────────┬──────────────────────────────┘
│ #include
▼
┌─────────────────────────────────────────────────┐
│ 你的 AimRT 项目 │
│ picture_service (参考 coreon-fusion 结构) │
│ ├── main.cpp 入口 + 注册模块 │
│ ├── module.h/cpp 模块类 │
│ ├── picture_rpc.h/cpp RPC 服务实现 │
│ └── install/linux/bin/ 运行时文件(em_run.sh+cfg)│
│ │ │
│ │ 继承 xxxSyncService │
│ │ 实现 GetPictureList() │
│ │ 调用 core_.GetRpcHandle().RegisterService()│
│ ▼ │
│ 编译 → picture_service 可执行文件 │
│ 启动 → ROS2 服务自动注册 │
└─────────────────────────────────────────────────┘
二、关键概念对比
| 纯 ROS2 (rclcpp) | AimRT 框架 | |
|---|---|---|
| 基类 | rclcpp::Node |
aimrt::ModuleBase |
| 入口函数 | rclcpp::init() + rclcpp::spin() |
AimRTCore + RegisterModule() |
| 注册服务 | create_service<>() |
core_.GetRpcHandle().RegisterService() |
| 配置方式 | 代码中手动配置 | YAML 配置文件 |
| 日志 | RCLCPP_INFO() |
AIMRT_INFO() |
| 服务类型生成 | rosidl_generate_interfaces() |
AimRT 自动从 interface 仓库拉取编译 |
三、完整项目结构 (对齐 coreon-fusion)
~/aimrt_picture_service/
├── build.sh # 编译脚本
├── CMakeLists.txt # 顶层 CMake
├── version.json # 版本号(指定拉取哪个分支的框架和接口)
├── cmake/
│ ├── GetAimRT.cmake # 拉取 AimRT 框架
│ └── GetAimRTe.cmake # 拉取 aimrte(编译接口协议)
└── src/
├── CMakeLists.txt # 子目录汇总
├── app/
│ ├── CMakeLists.txt # 可执行文件构建
│ └── main.cpp # 程序入口(AimRTCore + 注册模块)
├── module/
│ ├── CMakeLists.txt # 模块库构建
│ ├── module.h # 模块类声明
│ ├── module.cpp # 模块初始化 + 注册 RPC 服务
│ ├── picture_rpc.h # RPC 服务类声明(继承 SyncService)
│ └── picture_rpc.cpp # RPC 服务实现(实际业务逻辑)
└── install/ # ← 运行时文件(对标 coreon-fusion)
├── CMakeLists.txt # 安装脚本(拷贝文件到 build/install)
└── linux/bin/ # 运行时 bin 目录
├── em_run.sh # 启动脚本
└── cfg/
└── picture_service.yaml # AimRT 运行时配置
与 coreon-fusion 的对应关系:
coreon-fusion/ picture_service/
├── build.sh ├── build.sh
├── CMakeLists.txt ├── CMakeLists.txt
├── version.json ├── version.json
├── cmake/ ├── cmake/
│ ├── GetAimRT.cmake │ ├── GetAimRT.cmake
│ └── GetAimRTe.cmake │ └── GetAimRTe.cmake
└── src/ └── src/
├── CMakeLists.txt │ ├── CMakeLists.txt
├── app/ │ ├── app/
│ ├── CMakeLists.txt │ │ ├── CMakeLists.txt
│ └── main.cpp │ │ └── main.cpp
├── module/ │ ├── module/
│ ├── CMakeLists.txt │ │ ├── CMakeLists.txt
│ ├── module.h │ │ ├── module.h
│ ├── module.cpp │ │ ├── module.cpp
│ ├── coreon_rpc.h │ │ ├── picture_rpc.h
│ ├── coreon_rpc.cpp │ │ └── picture_rpc.cpp
│ ├── coreon_ch.h │ │ (不需要 Channel)
│ └── coreon_ch.cpp │ └── install/
└── install/ │ ├── CMakeLists.txt
├── CMakeLists.txt │ └── linux/bin/
└── linux/bin/ │ ├── em_run.sh
├── em_run.sh │ └── cfg/
└── cfg/ │ └── picture_service.yaml
└── coreon_fusion_...yaml
四、编译后安装目录结构
build/install/
├── bin/
│ ├── picture_service ← 可执行文件
│ ├── em_run.sh ← 启动脚本 (从 src/install/linux/bin 拷来)
│ └── cfg/
│ └── picture_service.yaml ← 配置文件 (从 src/install/linux/bin 拷来)
└── lib/
├── libpicture_service_module.so
├── libaimrt_ros2_plugin.so
├── libaimrte_runtime.so
├── libaimrte-utils.so ← 从 _deps/aimrte_runtime-src/lib 拷来
└── ...
五、关键文件详解
5.1 version.json --- 版本控制
json
{
"interface": "dev/take_photo",
"aimrte": "feat/t1"
}
interface:指定 interface 仓库的分支,你的 .srv 文件来自这个分支aimrte:指定 aimrte 框架的版本/分支
工作流程:
version.json
│
│ GetAimRTe.cmake 读取 "aimrte" → "feat/t1"
▼
cmake 自动 git clone aimrte 仓库的 feat/t1 分支
│
│ AIMRTE_BUILD_SOME_OF_ROBOT_PROTOCOLS "hal;mc;interaction"
▼
编译 interface 仓库的 hal/mc/interaction 目录
│
│ 生成 aimdk_msgs 包含你的接口类型
▼
你的代码 #include "aimdk_msgs/srv/GetPictureList.aimrt_rpc.srv.h"
5.2 CMakeLists.txt --- 顶层构建
cmake
cmake_minimum_required(VERSION 3.16)
project(picture_service VERSION 1.0.0)
set(CMAKE_CXX_STANDARD 20)
# 步骤1:拉取 AimRT 框架(v1.2.0)
include(cmake/GetAimRT.cmake)
# 步骤2:拉取 aimrte,编译 interface 仓库的接口协议
include(cmake/GetAimRTe.cmake)
# 步骤3:编译你自己的代码
add_subdirectory(src)
5.3 cmake/GetAimRT.cmake --- 拉取 AimRT 框架
cmake
include(FetchContent)
FetchContent_Declare(
aimrt
GIT_REPOSITORY https://${CODE_AGIBOT_COM_HTTP_TOKEN}code.iqiyuan-robot.com/software/qiyuan/modules/aimrt.git
GIT_TAG v1.2.0) # ← 框架版本
function(get_aimrt)
FetchContent_GetProperties(aimrt)
if(NOT aimrt_POPULATED)
set(AIMRT_BUILD_TESTS OFF)
set(AIMRT_BUILD_EXAMPLES OFF)
set(AIMRT_BUILD_DOCUMENT OFF)
set(AIMRT_BUILD_WITH_ROS2 ON) # 启用 ROS2 支持
set(AIMRT_BUILD_ROS2_PLUGIN ON) # 编译 ROS2 插件
set(AIMRT_BUILD_RUNTIME ON) # 编译运行时
set(AIMRT_USE_LOCAL_PROTOC_COMPILER OFF) # x86 编译用系统 protoc
# set(AIMRT_USE_LOCAL_PROTOC_COMPILER ON) # 交叉编译用本地 protoc
FetchContent_MakeAvailable(aimrt)
endif()
endfunction()
get_aimrt()
5.4 cmake/GetAimRTe.cmake --- 拉取 aimrte + 编译接口
cmake
# 1. 从 version.json 读取 aimrte 版本
function(__get_aimrte_version)
file(READ "${CMAKE_SOURCE_DIR}/version.json" json_string)
string(JSON aimrte_version GET "${json_string}" "aimrte")
set(AGIBOT_AIMRTE_VERSION "${aimrte_version}" PARENT_SCOPE)
endfunction()
__get_aimrte_version()
# 2. 拉取 aimrte
include(FetchContent)
FetchContent_Declare(
aimrte
GIT_REPOSITORY https://${CODE_AGIBOT_COM_HTTP_TOKEN}code.iqiyuan-robot.com/software/qiyuan/modules/aimrte.git
GIT_TAG ${AGIBOT_AIMRTE_VERSION})
# 3. 指定要编译的接口模块(关键!)
FetchContent_GetProperties(aimrte)
if(NOT aimrte_POPULATED)
set(AIMRTE_IMPORT_PROTOCOL_ONLY ON)
set(AIMRTE_BUILD_SOME_OF_ROBOT_PROTOCOLS "hal;mc;interaction")
# ↑ ↑ ↑
# hal是mc的依赖 mc interaction(你的接口在这里)
FetchContent_Populate(aimrte)
endif()
# 4. 交叉编译补丁(ARM 交叉编译时需要)
if(CMAKE_CROSSCOMPILING)
# ... 交叉编译 protoc 路径修复 ...
endif()
add_subdirectory("${aimrte_SOURCE_DIR}" "${aimrte_BINARY_DIR}")
install(FILES "${CMAKE_SOURCE_DIR}/version.json" DESTINATION ".")
aimrte_output(CMAKE_BUILD_RPATH)
协议依赖关系(决定 PROTOCOLS 的值):
common (自动编译,不需要写)
│
├── hal --- mc 的 srv 里用了 hal 的 JointState 等类型
├── mc --- interaction 的 InteractionState.msg 用了 mc 的 McPlayerState 等
└── interaction --- GetPictureList.srv / GetPicture.srv 在这里
如何确定依赖链: 如果编译报错 xxx__struct.hpp: No such file or directory,说明缺少某模块,把它加到列表里。
5.5 src/CMakeLists.txt --- 子目录汇总
cmake
add_subdirectory(module) # 1. 先编译模块库
add_subdirectory(app) # 2. 编译可执行文件(链接模块库)
add_subdirectory(install) # 3. 安装运行时文件到 build/install
5.6 src/app/main.cpp --- 程序入口
cpp
#include <csignal>
#include "aimrt_core.h"
#include "gflags/gflags.h"
#include "module/module.h"
DEFINE_string(cfg_file_path, "cfg/picture_service.yaml", "config file path");
using namespace aimrt::runtime::core;
using namespace picture;
AimRTCore *global_core_ptr = nullptr;
void SignalHandler(int sig) {
if (global_core_ptr && (sig == SIGINT || sig == SIGTERM)) {
global_core_ptr->Shutdown();
}
}
int32_t main(int32_t argc, char **argv) {
// 1. 解析命令行参数(gflags)
gflags::ParseCommandLineNonHelpFlags(&argc, &argv, true);
signal(SIGINT, SignalHandler);
signal(SIGTERM, SignalHandler);
// 2. 创建框架实例
AimRTCore core;
global_core_ptr = &core;
AimRTCore::Options options;
options.cfg_file_path = FLAGS_cfg_file_path;
// 3. 创建并注册模块
PictureModule picture_module;
core.GetModuleManager().RegisterModule(
picture_module.Info().name,
picture_module.NativeHandle());
// 4. 初始化 → 启动 → 关闭
core.Initialize(options); // 调 picture_module.Initialize()
core.Start(); // 调 picture_module.Start()
core.Shutdown(); // 调 picture_module.Shutdown()
return 0;
}
对比纯 ROS2 入口:
cpp
// 纯 ROS2
rclcpp::init(argc, argv);
auto node = std::make_shared<PictureService>();
rclcpp::spin(node);
rclcpp::shutdown();
5.7 src/module/module.h --- 模块类声明
cpp
#pragma once
#include "aimrt_module_cpp_interface/module_base.h"
#include "picture_rpc.h"
namespace picture {
class PictureModule : public aimrt::ModuleBase { // ← 继承框架基类
public:
PictureModule() = default;
~PictureModule() = default;
// 必须实现三个生命周期方法
aimrt::ModuleInfo Info() const override {
return aimrt::ModuleInfo{.name = "PictureModule"};
}
bool Initialize(aimrt::CoreRef core) override; // 初始化
bool Start() override; // 启动
void Shutdown() override; // 关闭
private:
aimrt::CoreRef core_; // 框架句柄(万能管家)
std::shared_ptr<GetPictureListServiceImpl> rpc_get_picture_list_;
std::shared_ptr<GetPictureServiceImpl> rpc_get_picture_;
};
} // namespace picture
aimrt::CoreRef 是万能句柄,通过它可以获取:
core_.GetLogger()--- 日志core_.GetRpcHandle()--- RPC 服务注册core_.GetConfigurator()--- 配置管理core_.GetChannelHandle()--- Topic 发布/订阅(本次未使用)
5.8 src/module/module.cpp --- 模块初始化 + 注册 RPC 服务
cpp
#include "module.h"
namespace picture {
bool PictureModule::Initialize(aimrt::CoreRef core) {
core_ = core; // 保存框架句柄
try {
// 步骤1:创建 RPC 服务实例
rpc_get_picture_list_ = std::make_shared<GetPictureListServiceImpl>(
core_.GetLogger());
rpc_get_picture_ = std::make_shared<GetPictureServiceImpl>(
core_.GetLogger());
// 步骤2:注册 RPC 服务(核心语句!)
bool ret = core_.GetRpcHandle().RegisterService(
rpc_get_picture_list_.get());
AIMRT_INFO("Register GetPictureList service result: {}", ret);
ret = core_.GetRpcHandle().RegisterService(
rpc_get_picture_.get());
AIMRT_INFO("Register GetPicture service result: {}", ret);
} catch (const std::exception& e) {
AIMRT_ERROR("PictureModule init failed: {}", e.what());
return false;
}
AIMRT_INFO("PictureModule init succeeded.");
return true;
}
bool PictureModule::Start() {
AIMRT_INFO("PictureModule started.");
return true;
}
void PictureModule::Shutdown() {
AIMRT_INFO("PictureModule shutdown.");
}
} // namespace picture
核心语句解析:
cpp
core_.GetRpcHandle().RegisterService(ptr);
// ↑ ↑
// 框架万能句柄 注册服务到框架
// 框架自动做三件事:
// 1. 读取 xxxSyncService 的类型名
// 2. 在 ROS2 网络上暴露服务
// 3. 映射服务名: aimdk_msgs/srv/GetPictureList → /aimdk_5Fmsgs/srv/GetPictureList
这一行等价于纯 ROS2 的 this->create_service<>()。
5.9 src/module/picture_rpc.h --- RPC 服务类声明(核心!)
cpp
#pragma once
#include <memory>
#include <vector>
#include <string>
#include <filesystem>
#include <fstream>
#include "aimrt_module_cpp_interface/core.h"
#include "core/aimrt_core.h"
// ★ 这两个头文件是自动生成的!编译时从 interface 仓库编译而来 ★
#include "aimdk_msgs/srv/GetPictureList.aimrt_rpc.srv.h"
#include "aimdk_msgs/srv/GetPicture.aimrt_rpc.srv.h"
using namespace aimrt::runtime::core;
namespace fs = std::filesystem;
namespace picture {
// ============================================================
// GetPictureList 服务
// 继承自动生成的 SyncService 基类
// ============================================================
class GetPictureListServiceImpl
: public aimdk_msgs::srv::GetPictureListSyncService {
public:
explicit GetPictureListServiceImpl(aimrt::logger::LoggerRef logger)
: logger_(logger) {}
~GetPictureListServiceImpl() override = default;
// ★ 实现服务处理方法(收到 RPC 请求时自动回调) ★
aimrt::rpc::Status GetPictureList(
aimrt::rpc::ContextRef ctx_ref,
const aimdk_msgs::srv::GetPictureList_Request& req, // 请求
aimdk_msgs::srv::GetPictureList_Response& rsp) // 响应
override;
private:
aimrt::logger::LoggerRef logger_;
std::string image_dir_{"/home/user/images"}; // 图片存储目录
};
// ============================================================
// GetPicture 服务
// ============================================================
class GetPictureServiceImpl
: public aimdk_msgs::srv::GetPictureSyncService {
public:
explicit GetPictureServiceImpl(aimrt::logger::LoggerRef logger)
: logger_(logger) {}
~GetPictureServiceImpl() override = default;
aimrt::rpc::Status GetPicture(
aimrt::rpc::ContextRef ctx_ref,
const aimdk_msgs::srv::GetPicture_Request& req,
aimdk_msgs::srv::GetPicture_Response& rsp) override;
private:
aimrt::logger::LoggerRef logger_;
std::string image_dir_{"/home/user/images"};
};
} // namespace picture
继承链(从接口到实现):
interface/ros2/robot/interaction/srv/GetPictureList.srv ← 组长写的接口定义
↓ aimrte 编译
aimdk_msgs/srv/GetPictureList.aimrt_rpc.srv.h ← 自动生成
↓ 包含
class GetPictureListSyncService { ← 自动生成的基类
virtual aimrt::rpc::Status GetPictureList(...) = 0;
};
↓ 你继承
class GetPictureListServiceImpl : public GetPictureListSyncService { ← 你的实现
aimrt::rpc::Status GetPictureList(...) override {
// 你的业务逻辑
}
};
5.10 src/module/picture_rpc.cpp --- RPC 服务实现(业务逻辑)
cpp
#include "picture_rpc.h"
namespace picture {
// ============================================================
// GetPictureList --- 获取图片列表
// ============================================================
aimrt::rpc::Status GetPictureListServiceImpl::GetPictureList(
aimrt::rpc::ContextRef ctx_ref,
const aimdk_msgs::srv::GetPictureList_Request& req,
aimdk_msgs::srv::GetPictureList_Response& rsp) {
AIMRT_INFO("收到 GetPictureList 请求");
std::vector<std::string> extensions = {".jpg", ".jpeg", ".png", ".gif", ".bmp"};
try {
for (const auto& entry : fs::directory_iterator(image_dir_)) {
if (!entry.is_regular_file()) continue;
std::string ext = entry.path().extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
if (std::find(extensions.begin(), extensions.end(), ext) != extensions.end()) {
// 文件名(时间戳)作为 picture_id
aimdk_msgs::msg::PictureInfo info;
info.picture_id = std::stoll(entry.path().stem().string());
rsp.picture_list.push_back(info);
}
}
} catch (const std::exception& e) {
AIMRT_ERROR("读取图片目录失败: {}", e.what());
rsp.header.status.value = 2; // FAILURE
return {};
}
rsp.header.status.value = 1; // SUCCESS
AIMRT_INFO("GetPictureList 返回 {} 张图片", rsp.picture_list.size());
return {};
}
// ============================================================
// GetPicture --- 根据 picture_id 获取单张图片
// ============================================================
aimrt::rpc::Status GetPictureServiceImpl::GetPicture(
aimrt::rpc::ContextRef ctx_ref,
const aimdk_msgs::srv::GetPicture_Request& req,
aimdk_msgs::srv::GetPicture_Response& rsp) {
AIMRT_INFO("收到 GetPicture 请求, picture_id: {}", req.picture_id);
std::vector<std::string> extensions = {".jpg", ".jpeg", ".png", ".gif", ".bmp"};
std::string image_path;
bool found = false;
for (const auto& ext : extensions) {
std::string test_path = image_dir_ + "/" + std::to_string(req.picture_id) + ext;
if (fs::exists(test_path)) {
image_path = test_path;
found = true;
break;
}
}
if (!found) {
AIMRT_ERROR("图片不存在: picture_id {}", req.picture_id);
rsp.header.status.value = 2; // FAILURE
return {};
}
std::ifstream file(image_path, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
rsp.header.status.value = 2;
return {};
}
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> buffer(size);
if (file.read(reinterpret_cast<char*>(buffer.data()), size)) {
rsp.picture_data = buffer;
rsp.header.status.value = 1; // SUCCESS
AIMRT_INFO("图片读取成功: {}, 大小: {} bytes", image_path, size);
} else {
rsp.header.status.value = 2;
}
return {};
}
} // namespace picture
关键点:
rsp.header.status.value = 1表示 SUCCESS,= 2表示 FAILUREreturn {}表示 RPC 调用成功(空 Status)- 图片以时间戳命名如
1747315200.jpg,通过fs::directory_iterator遍历
5.11 src/install/CMakeLists.txt --- 安装运行时文件 ★对齐 coreon-fusion★
cmake
# 将运行时文件拷贝到安装目录
add_custom_target(
picture_service_install_build_all ALL
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/linux/bin # 源: src/install/linux/bin/
${CMAKE_INSTALL_PREFIX}/bin # 目标: build/install/bin/
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_BINARY_DIR}/_deps/aimrte_runtime-src/lib # 源: aimrte 运行时库
${CMAKE_INSTALL_PREFIX}/lib # 目标: build/install/lib/
DEPENDS picture_service) # 依赖可执行文件,编译后执行
这个文件做了两件事(对齐 coreon-fusion 的标准做法):
- 拷贝运行时文件 :
src/install/linux/bin/*(em_run.sh, cfg/) →build/install/bin/ - 拷贝 aimrte 运行时库 :
build/_deps/aimrte_runtime-src/lib/*.so→build/install/lib/
核心价值: 解决了 libaimrte-utils.so 等 amete 运行时库缺失的问题。安装后所有库都在 build/install/lib/ 下,em_run.sh 只需 LD_LIBRARY_PATH=../lib 一行即可。
5.12 src/install/linux/bin/em_run.sh --- 启动脚本 ★对齐 coreon-fusion★
bash
#!/bin/bash
source /opt/ros/humble/setup.bash # ROS2 环境
cd $(dirname $0) # 进入 bin 目录
export LD_LIBRARY_PATH=../lib:./:$LD_LIBRARY_PATH
# 库路径:install/lib + 当前目录
./picture_service --cfg_file_path=cfg/picture_service.yaml
# 启动服务,指定配置文件
对比 coreon-fusion 的 em_run.sh:
bash
# coreon-fusion/src/install/linux/bin/em_run.sh
source /opt/ros/humble/setup.bash
cd $(dirname $0)
export LD_LIBRARY_PATH=../lib:./:$LD_LIBRARY_PATH
./coreon_fusion --cfg_file_path=cfg/coreon_fusion_config.yaml
完全一致! 这就是 AimRT 项目的标准启动方式。
5.13 src/install/linux/bin/cfg/picture_service.yaml --- AimRT 配置
yaml
aimrt:
log: # 日志配置
core_lvl: Info
default_module_lvl: Info
backends:
- type: console # 输出到控制台
executor: # 线程池配置
executors:
- name: picture_executor
type: asio_thread
options:
thread_num: 2
plugin: # 插件配置(★必须!★)
plugins:
- name: ros2_plugin # 加载 ROS2 通信插件
path: libaimrt_ros2_plugin.so
options:
node_name: picture_service # ROS2 节点名
rpc: # RPC 服务配置
backends:
- type: ros2 # 使用 ROS2 作为 RPC 后端
servers_options:
- func_name: "(.*)" # 匹配所有服务函数,注册为 ROS2 服务
enable_backends: [ros2] # 在 ROS2 上暴露
channel: # Topic 配置(本服务暂不需要)
backends:
- type: ros2
PictureModule: # 模块级配置
executor_name: "picture_executor"
5.14 src/app/CMakeLists.txt --- 可执行文件构建
cmake
set(CUR_TARGET_NAME picture_service)
file(GLOB_RECURSE src ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
add_executable(${CUR_TARGET_NAME})
target_sources(${CUR_TARGET_NAME} PRIVATE ${src})
target_include_directories(${CUR_TARGET_NAME}
PRIVATE "${PROJECT_SOURCE_DIR}/src")
target_link_libraries(${CUR_TARGET_NAME}
PRIVATE gflags::gflags # 命令行参数解析
aimrt::runtime::core # AimRT 运行时核心
picture_service_module) # 你的模块库
set_target_properties(${CUR_TARGET_NAME} PROPERTIES
OUTPUT_NAME "picture_service")
install(TARGETS ${CUR_TARGET_NAME} RUNTIME DESTINATION bin)
5.15 src/module/CMakeLists.txt --- 模块库构建
cmake
set(CUR_TARGET_NAME picture_service_module)
file(GLOB_RECURSE src ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
add_library(${CUR_TARGET_NAME} SHARED)
target_sources(${CUR_TARGET_NAME} PRIVATE ${src})
target_include_directories(${CUR_TARGET_NAME}
PUBLIC "${PROJECT_SOURCE_DIR}/src")
target_link_libraries(${CUR_TARGET_NAME}
PUBLIC aimrt::interface::aimrt_module_cpp_interface
aimrt::interface::aimrt_module_ros2_interface
aimrt::common::util
aimrt::runtime::core # 提供 core/aimrt_core.h
aimdk-protocol-ROS) # 接口协议库(包含你的 srv)
set_target_properties(${CUR_TARGET_NAME} PROPERTIES
OUTPUT_NAME "picture_service_module")
install(TARGETS ${CUR_TARGET_NAME} LIBRARY DESTINATION lib)
六、接口定义到最终可用的完整流程
第一步:组长在 interface 仓库定义接口
───────────────────────────────────────────
interface/ros2/robot/interaction/srv/GetPictureList.srv
├── CommonRequest header ← 引用 common 目录的 CommonRequest.msg
├── CommonResponse header ← 引用 common 目录的 CommonResponse.msg
└── PictureInfo[] picture_list ← 引用 interaction/msg/PictureInfo.msg
interface/ros2/robot/interaction/srv/GetPicture.srv
├── CommonRequest header
├── int64 picture_id
├── CommonResponse header
└── uint8[] picture_data
第二步:配置 version.json 指定版本
───────────────────────────────────────────
{
"interface": "dev/take_photo", ← interface 仓库分支(组长给的)
"aimrte": "feat/t1" ← aimrte 框架分支(组长给的)
}
第三步:配置协议编译范围
───────────────────────────────────────────
GetAimRTe.cmake:
set(AIMRTE_BUILD_SOME_OF_ROBOT_PROTOCOLS "hal;mc;interaction")
# ↑ ↑ ↑
# interaction 接口所在的目录
# interaction 依赖 mc 的类型
# mc 依赖 hal 的类型
确认依赖链:
common (自动编译)
│
├── hal --- mc 依赖 hal 的 JointState 等
├── mc --- interaction 的 InteractionState.msg 用了 mc 的 McPlayerState 等
└── interaction --- GetPictureList.srv / GetPicture.srv 在这里
第四步:cmake 编译时自动拉取编译接口
───────────────────────────────────────────
顶层 CMakeLists.txt:
include(cmake/GetAimRT.cmake) → git clone aimrt 框架 (v1.2.0)
include(cmake/GetAimRTe.cmake) → git clone aimrte (feat/t1)
│ │ 读取 version.json
│ │ set(AIMRTE_BUILD_SOME_OF_ROBOT_PROTOCOLS
│ │ "hal;mc;interaction")
│ │
│ ▼
│ 编译 interface 仓库的 hal/mc/interaction 目录
│ │
│ ▼
│ 生成 aimdk_msgs 包
│ ├── GetPictureList.aimrt_rpc.srv.h
│ │ ├── GetPictureListSyncService ← 你继承的基类
│ │ ├── GetPictureList_Request ← 请求类型
│ │ └── GetPictureList_Response ← 响应类型
│ └── GetPicture.aimrt_rpc.srv.h
│ ├── GetPictureSyncService
│ ├── GetPicture_Request
│ └── GetPicture_Response
│
add_subdirectory(src) → 编译你的代码
├── add_subdirectory(module) → 编译模块库 (picture_rpc.cpp 引用生成的 .h)
├── add_subdirectory(app) → 编译可执行文件 (链接模块库)
└── add_subdirectory(install)→ 拷贝运行时文件到 build/install/
第五步:写 RPC 服务实现代码
───────────────────────────────────────────
picture_rpc.h:
class GetPictureListServiceImpl
: public aimdk_msgs::srv::GetPictureListSyncService { ← 继承自动生成的基类
aimrt::rpc::Status GetPictureList( ← 实现服务方法
aimrt::rpc::ContextRef ctx_ref,
const GetPictureList_Request& req, ← 请求参数
GetPictureList_Response& rsp) override; ← 响应(你填充)
};
picture_rpc.cpp:
aimrt::rpc::Status GetPictureListServiceImpl::GetPictureList(...) {
// 从本地目录读取图片文件
for (const auto& entry : fs::directory_iterator(image_dir_)) {
PictureInfo info;
info.picture_id = std::stoll(entry.path().stem().string());
rsp.picture_list.push_back(info);
}
rsp.header.status.value = 1; // SUCCESS
return {}; // RPC 成功
}
module.cpp:
bool PictureModule::Initialize(aimrt::CoreRef core) {
core_ = core;
rpc_get_picture_list_ = std::make_shared<GetPictureListServiceImpl>(...);
core_.GetRpcHandle().RegisterService(rpc_get_picture_list_.get());
// ↑ 这一行注册服务,框架自动映射到 ROS2
}
第六步:框架自动映射服务名
───────────────────────────────────────────
RegisterService() 后,框架启动时输出:
Initialize Report → [12]. Ros2 Rpc Backend Server Info:
┌─────────────────────────────────────┬──────────────────────────────────┐
│ func name │ ros2 func name │
├─────────────────────────────────────┼──────────────────────────────────┤
│ ros2:/aimdk_msgs/srv/GetPictureList │ /aimdk_5Fmsgs/srv/GetPictureList │
│ ros2:/aimdk_msgs/srv/GetPicture │ /aimdk_5Fmsgs/srv/GetPicture │
└─────────────────────────────────────┴──────────────────────────────────┘
func name: 内部标识 (类型路径)
ros2 func name: ROS2 网络上的服务名 (_5F 是 _ 的 URL 编码,框架自动处理)
第七步:启动服务
───────────────────────────────────────────
cd build/install/bin
./em_run.sh
日志确认:
[Info] PictureModule: Register GetPictureList service result: true ✓
[Info] PictureModule: Register GetPicture service result: true ✓
[Info] AimRT startup completed, will wait for shutdown. ✓
第八步:测试调用
───────────────────────────────────────────
# 查看服务列表
ros2 service list | grep Picture
# 查看接口定义
ros2 interface show aimdk_msgs/srv/GetPictureList
# 调用 GetPictureList
ros2 service call /aimdk_5Fmsgs/srv/GetPictureList \
aimdk_msgs/srv/GetPictureList \
"{header: {header: {stamp: {sec: 0, nanosec: 0}}}}"
# 调用 GetPicture
ros2 service call /aimdk_5Fmsgs/srv/GetPicture \
aimdk_msgs/srv/GetPicture \
"{header: {header: {stamp: {sec: 0, nanosec: 0}}}, picture_id: 1747315200}"
# Python 测试
from aimdk_msgs.srv import GetPictureList, GetPicture
client = node.create_client(GetPictureList, "/aimdk_5Fmsgs/srv/GetPictureList")
resp = client.call(GetPictureList.Request())
print(f"status={resp.header.status.value}, count={len(resp.picture_list)}")
七、开发新服务的步骤总结
1. 组长在 interface 仓库定义 .srv/.msg 文件
2. 你创建 AimRT 工程,对标 coreon-fusion 目录结构
3. 配置 version.json(interface 和 aimrte 的分支)
4. 配置 GetAimRTe.cmake 的 AIMRTE_BUILD_SOME_OF_ROBOT_PROTOCOLS
5. 确认接口依赖链(编译报错缺啥加啥)
6. 写 module.h/cpp(继承 aimrt::ModuleBase)
7. 写 picture_rpc.h(继承 xxxSyncService,声明处理方法)
8. 写 picture_rpc.cpp(实现业务逻辑,填充 Response)
9. 在 module.cpp 的 Initialize() 中 RegisterService()
10. 写 main.cpp(AimRTCore + gflags + RegisterModule + Initialize/Start/Shutdown)
11. 写 cfg/*.yaml(ros2_plugin + RPC ros2 后端 + 模块配置)
12. 写 src/install/CMakeLists.txt(拷贝运行时文件 + aimrte 库)
13. 写 em_run.sh(放到 src/install/linux/bin/)
14. ./build.sh 编译
15. cd build/install/bin && ./em_run.sh 启动
16. ros2 service call 测试
八、常见问题
Q: 编译报错 xxx__struct.hpp: No such file or directory?
接口依赖链不完整。找到缺失的类型属于哪个模块,把它加到 AIMRTE_BUILD_SOME_OF_ROBOT_PROTOCOLS。例如 McPlayerState 在 mc 模块 → 加 mc。
Q: 启动报 libaimrt_ros2_plugin.so 或 libaimrte-utils.so 找不到?
原因是 src/install/CMakeLists.txt 没有拷贝 amete 运行时库。确认 install/CMakeLists.txt 里第2个 copy_directory 命令正确,且 em_run.sh 里 LD_LIBRARY_PATH 包含 ../lib。
Q: 启动成功但 ros2 service call 报 type invalid?
宿主机需要加载环境。在宿主机执行:
bash
export LD_LIBRARY_PATH=~/aimrt_picture_service/build/install/lib:$LD_LIBRARY_PATH
export AMENT_PREFIX_PATH=~/aimrt_picture_service/build/install:$AMENT_PREFIX_PATH
Q: _5F 是什么?
AimRT ROS2 插件对类型名中 _ 的 URL 编码,框架自动处理,不影响使用。
Q: 如何加 Topic 发布/订阅?
参考 coreon-fusion 的 coreon_ch.h/cpp,通过 core_.GetChannelHandle() 操作 PublisherRef 和订阅回调。
Q: x86 和 ARM 交叉编译的区别?
- x86:
AIMRT_USE_LOCAL_PROTOC_COMPILER OFF,用系统 protoc,编译后可直接本地跑 - ARM:
AIMRT_USE_LOCAL_PROTOC_COMPILER ON,用自带 protoc,需要用aima cross build或交叉编译容器