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 或交叉编译容器
相关推荐
神仙别闹44 分钟前
基于C语言实现(控制台)学生信息管理系统
c语言·开发语言
fuquxiaoguang1 小时前
架构模式革新:用“旁路镜像”改造老旧系统——中间件驱动的渐进式AI落地范式
人工智能·中间件·架构
ch.ju1 小时前
Java Programming Chapter 3——Default value of array
java·开发语言
aini_lovee1 小时前
STM32 上实现 SD 卡读取 JPEG 解码 TFT 显示
开发语言·stm32
谙弆悕博士1 小时前
【附C语言源码】C语言 栈结构 实现及其扩展操作
c语言·开发语言·数据结构·算法·链表·指针·
njsgcs1 小时前
c# solidworks GetPartBox无法获得正确实体边界框原因
开发语言·c#·solidworks
bandaoyu1 小时前
【CUDA】store/load普通访存 vs 非临时(Non-Temporal)访存
java·开发语言·redis
逍遥德1 小时前
常见的任务调度框架介绍
java·spring boot·中间件
music score1 小时前
google 的C++自动化测试框架详解(Google Test)(2)
c++·qt·lucene