aimrt中间件的使用

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 表示 FAILURE
  • return {} 表示 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 的标准做法):

  1. 拷贝运行时文件src/install/linux/bin/* (em_run.sh, cfg/) → build/install/bin/
  2. 拷贝 aimrte 运行时库build/_deps/aimrte_runtime-src/lib/*.sobuild/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。例如 McPlayerStatemc 模块 → 加 mc

Q: 启动报 libaimrt_ros2_plugin.solibaimrte-utils.so 找不到?

原因是 src/install/CMakeLists.txt 没有拷贝 amete 运行时库。确认 install/CMakeLists.txt 里第2个 copy_directory 命令正确,且 em_run.sh 里 LD_LIBRARY_PATH 包含 ../lib

Q: 启动成功但 ros2 service calltype 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 或交叉编译容器
相关推荐
用户805533698038 小时前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner8 小时前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz5 天前
QML Hello World 入门示例
qt
xcyxiner8 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner9 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner9 天前
DicomViewer (添加模型类)3
qt
xcyxiner10 天前
DicomViewer (目录调整) 2
qt
xcyxiner10 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00612 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术12 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript