5. 二次开发程序部署

本章节将完整介绍智元提供的二次开发工具链架构及使用方式。

5.1 开发工具介绍及安装

5.1.1 系统要求

  • 操作系统: Ubuntu 22.04 LTS 或更高版本(推荐 Ubuntu 22.04 LTS)
  • 其他支持的系统:
    • macOS 11.0 (Big Sur) 或更高版本
    • Windows 10/11 专业版或企业版(需启用 WSL2)
  • 硬件配置
    • CPU: 8核心或以上(推荐 16核心)
    • 内存: 至少 32GB RAM(推荐 64GB 或更高)
    • 存储空间: 至少 100GB 可用磁盘空间(用于 Docker 镜像和容器)
    • 网络: 稳定的互联网连接(用于下载镜像和依赖)

5.1.2 创建容器开发环境

建议在运行安装脚本前,先在主机上完成 Docker和 vscode 的安装和配置。 1.在AimDK下载中心获取AimDK-A3-v3.3-Bootstrap压缩包后解压,解压后目录结构如下:

  • devcontainer.json # 定义开发容器的配置
  • setup_aimde.sh # 开发环境的安装脚本

1.执行安装脚本,安装开发环境

bash
./setup_aimde.sh

提示: 若这是您首次配置 Docker,系统可能会提示您重启电脑以应用用户组权限设置。重启完成后,重新运行安装脚本即可继续完成安装。

  • 自动打开vscode后,vscode 右下角的通知会出现弹框, 点击 Reopen in Container
  • 在 vscode 中打开新的 Terminal, 输入 setup-init 并根据提示操作即可
  • 看到 🎉🎉🎉 Setup successful 代表 setup 成功, 可以开始开发并构建项目

2.复制 DevKit 包到挂载目录

在AimDK下载中心获取AimDK-A3-v3.3-DevKit压缩包,将 DevKit 包复制到主机的挂载目录 ~/sdk/a3/aimde/,该目录会自动映射到容器内的 /workspaces/aimde/ 路径。

bash
# 在主机上执行
cp *sdk-package-a3-v3.3.13-0-gb889a630d-2610080917-devkit.tar.gz ~/sdk/a3/aimde/

5.1.3 容器内容介绍

/sdk目录

SDK 根目录,包含通信协议定义、Python 协议包、使用示例,以及开发工具链、Bazel 构建规则和多架构(x86_64 / aarch64)预编译库,用于支持项目的编译、开发与部署。

python
/sdk
├── aima_protocol/                  # SDK 通信协议定义(Protobuf / ROS 2 消息)
├── aima_protocol_py_pkg/           # 协议的 Python 封装包
│   └── aimdk_proto-0.0.0+local-py3-none-any.whl   # 可直接 pip 安装的 wheel
├── aimdk_examples/                 # SDK 使用示例
└── dev_tools/                      # 开发与构建工具
    ├── x86_64/                     # SDK 预编译库(x86_64 架构)
    ├── rk3588_aarch64/             # SDK 预编译库(RK3588 / aarch64 架构)
    ├── rule_ros2/                  # ROS 2 的 Bazel 构建规则
    ├── sdk-buildchain/             # 集成编译仓库(统一构建入口)
    └── third_party_libs/           # 第三方依赖库

/opt/rockship目录

提供RK3588平台的交叉编译工具链和部分三方依赖库。

bash
/opt/rockship 
├── rk3588_sysroot  # ARM架构的三方库
└── rk3588-toolchain # 交叉编译工具链

/opt/nvidia

提供Nvidia Thor平台的交叉编译工具链和部分三方依赖库。

bash
/opt/rockship 
├── thor_sysroot  # ARM架构的三方库
└── thor-toolchain # 交叉编译工具链

/workspaces/aimde目录

开发工作区,包含 AimDK 通信协议、示例代码及开发容器配置。其中 aima_protocol 和 aimdk_examples 由 setup-init 从 SDK 同步而来,可在此直接修改、编译和调试。

bash
/workspaces/aimde
├── aima_protocol # SDK 通信协议定义(Protobuf / ROS 2 消息)
├── aimdk_examples # SDK 使用示例
└── .devcontainer # 开发容器配置(镜像、挂载、环境变量等)

5.2 更新方式

为了满足不同开发阶段的需求,同时兼顾更新效率和环境稳定性,SDK 提供了两种灵活的更新方式。随着 SDK 功能的持续迭代、bug 修复以及性能优化,定期更新能够帮助开发者:

  • 获取最新的 API 接口和功能特性
  • 修复已知问题,提升开发体验和系统稳定性
  • 保持与官方文档和示例代码的版本一致性
  • 享受性能优化带来的编译和运行效率提升

根据更新内容的不同,开发者可以选择适合的更新策略,既能保证环境的完整性,又能提高更新效率:

完整镜像更新

适用场景:

  • 重大版本升级: SDK 框架、开发工具链、系统依赖等核心组件发生重大变更
  • 环境彻底重置: 需要全新的开发环境,清除所有历史配置和缓存
  • 跨版本更新: 从较旧版本直接升级到最新版本
  • 系统层面变更: 容器基础镜像、编译工具链、系统库等底层组件更新
  • 首次部署: 新用户初次搭建开发环境

特点:

  • ✅ 确保环境完整性和一致性
  • ✅ 包含所有依赖和配置的最新版本
  • ⚠️ 更新时间较长(需下载、加载完整镜像)
  • ⚠️ 容器重建后,未持久化的数据会丢失

DevKit 包更新

适用场景:

  • SDK 工具包迭代: 仅 SDK API、库文件、开发工具包等内容更新
  • 快速补丁修复: 修复 SDK 中的 bug 或添加小功能
  • 频繁版本迭代: 开发阶段需要频繁测试不同 DevKit 版本
  • 保留容器配置: 需要保持现有容器环境和个人配置不变
  • 小版本更新: 从 v3.3-rc11 更新到 v3.3-rc12 等小版本变化

特点:

  • ✅ 更新速度快(仅替换 /sdk/ 目录内容)
  • ✅ 保留容器配置和用户数据
  • ✅ 适合频繁迭代和测试
  • ⚠️ 仅更新 SDK 工具包,不更新容器基础环境
  • ⚠️ 如底层依赖变更,可能需要完整镜像更新

5.2.2 如何选择更新方式?

更新内容推荐方式
SDK 大版本升级(如 v3.2 → v3.3)完整镜像更新
SDK 小版本升级(如 v3.3-rc11 → v3.3-rc12)DevKit 包更新
编译工具链更新完整镜像更新
API 接口变更或新增DevKit 包更新
系统依赖库升级完整镜像更新
Bug 修复补丁DevKit 包更新
容器环境出现异常完整镜像更新

5.2.3 完整镜像更新

  1. 更新镜像版本配置

编辑 .devcontainer/devcontainer.json 文件,将 image 字段更新为新镜像版本号: tongyong-public-cn-shanghai.cr.volces.com/sdk/sdk-image-a3-v3.3.13-0-gb889a630d-2610080917:latest

  • 重建容器

在 VS Code 中执行命令:

  • 按 F1 或 Ctrl+Shift+P 打开命令面板
  • 输入并选择: Dev Containers: Rebuild and Reopen in Container

5.2.4 DevKit 包更新

  1. 复制 DevKit 包到挂载目录

在AimDK下载中心获取AimDK-A3-v3.3-DevKit压缩包,将 DevKit 包复制到主机的挂载目录 ~/sdk/a3/aimde/,该目录会自动映射到容器内的 /workspaces/aimde/ 路径。

bash
# 在主机上执行
cp *sdk-package-a3-v3.3.13-0-gb889a630d-2610080917-devkit.tar.gz ~/sdk/a3/aimde/
  • 在容器内替换 DevKit 包

进入 DevContainer 终端,执行以下命令:

bash
# 清理旧版本
sudo rm -rf /sdk/*

# 解压新版本到 /sdk/ 目录
sudo tar -xvf /workspaces/aimde/*sdk-package-a3-v3.3.13-0-gb889a630d-2610080917-devkit.tar.gz -C /sdk/

# 清理压缩包(可选)
rm -rf /workspaces/aimde/*sdk-package-a3-v3.3.13-0-gb889a630d-2610080917-devkit.tar.gz

5.2.5 更新工作区项目

SDK 版本更新后,需要同步工作区中的协议定义和示例代码。推荐按以下步骤操作:

1. 删除旧版本目录

bash
rm -rf /workspaces/aimde/aima_protocol /workspaces/aimde/aimdk_examples

⚠️ 此操作会删除这两个目录下的所有本地修改。如有改动需要保留,请先备份或提交到版本库。

2. 重新初始化工作区

bash
setup-init

setup-init 会从 SDK 重新拉取最新的 aima_protocol 和 aimdk_examples 到工作区。

5.2.6 注意事项

  • 更新前建议备份重要数据和配置文件
  • 确保 Docker 服务正常运行
  • 完整镜像更新会重建容器,容器内未持久化的数据将丢失
  • DevKit 更新仅替换 SDK 工具包,不影响容器配置

5.3 第三方库管理

构建框架依赖的常用三方库已预先集成在本地开发环境(aimde)中,通过 Bazel rule 统一管理,用户只需在业务代码中按规范引入即可,无需自行下载和安装。同时,框架也支持用户按需自行添加三方库,并以相同的方式进行管理和引用。

5.3.1 三方库定义链条

三方库从本地文件到业务代码,经过三层定义,每层职责单一:

plain
/sdk/dev_tools/third_party_libs/external/xxx        # 库文件(源码或预编译)
        │  ① sdk_third_party_deps.bzl:local_repository 注册为 Bazel 外部仓库
        ▼
@xxx / @xxx_x86_64 / @xxx_aarch64                    # 外部仓库
        │  ② integration/BUILD:alias + select 屏蔽架构与来源差异
        ▼
@integration//:xxx                                   # SDK 统一入口
        │  ③ aimdk_examples/thirdparty/BUILD:alias 转发到业务仓本地
        ▼
//thirdparty:xxx                                     # 业务代码依赖此目标

5.3.2 注册外部仓库

所有三方库统一存放在 /sdk/dev_tools/third_party_libs/external/ 下,通过 /sdk/dev_tools/sdk-buildchain/projects/external/sdk_third_party_deps.bzl 中local_repository 注册:

  • 源码构建的库注册一个仓库,由 Bazel 按当前工具链编译,天然支持多架构
  • 预编译库按架构分别注册,仓库名以 _x86_64 / _aarch64 结尾
java
def third_party_deps():
    # 源码构建:单一仓库
    native.local_repository(
        name = "jsoncpp",
        path = "/sdk/dev_tools/third_party_libs/external/jsoncpp",
    )

    # 预编译:每个架构一个仓库
    native.local_repository(
        name = "boost_x86_64",
        path = "/sdk/dev_tools/third_party_libs/external/boost_x86_64",
    )
    native.local_repository(
        name = "boost_aarch64",
        path = "/sdk/dev_tools/third_party_libs/external/boost_aarch64",
    )

5.3.3 统一入口

通过/sdk/dev_tools/sdk-buildchain/BUILD 中 alias 对外暴露与架构无关的名称,预编译库用 select 按目标平台自动切换:

bash
# 预编译:按架构选择
alias(
    name = "boost",
    actual = select({
        "@integration//toolchains/platforms:is_aarch64": "@boost_aarch64//:boost",
        "//conditions:default": "@boost_x86_64//:boost",
    }),
    visibility = ["//visibility:public"],
)

# 源码构建:直接转发
alias(
    name = "jsoncpp",
    actual = "@jsoncpp",
    visibility = ["//visibility:public"],
)

5.3.4 业务仓转发

aimdk_examples/thirdparty/BUILD 下再做一层转发,本仓所用的三方库一目了然:

python
alias(
    name = "boost",
    actual = "@integration//:boost",
)

alias(
    name = "jsoncpp",
    actual = "@integration//:jsoncpp",
)

业务代码只依赖 //thirdparty:xxx:

python
cc_binary(
    name = "demo",
    srcs = ["main.cc"],
    deps = [
        "//thirdparty:boost",
        "//thirdparty:jsoncpp",
    ],
)

5.3.5 新增三方库

  1. 将库文件放入 /sdk/dev_tools/third_party_libs/external/(预编译库按架构分目录)
  2. 在 sdk_third_party_deps.bzl 中用 local_repository 注册
  3. 在 integration/BUILD 中添加 alias(预编译库使用 select)
  4. 在业务仓 thirdparty/BUILD 中添加转发 alias

业务代码不要直接依赖 @xxx、@xxx_x86_64、@xxx_aarch64 或 @integration//:xxx,统一通过 //thirdparty:xxx 引用。这样依赖的来源或架构方式变化时,只需改上游定义,业务代码无需变动。

5.4 SDK依赖说明

所有 SDK 依赖已预先集成在本地开发环境(aimde)中,通过 Bazel 统一管理。用户无需手动下载安装,只需在业务代码中按规范引入即可使用。

说明: 此处的 SDK 特指智元内部代码对外暴露的头文件(.h)及动态库(.so),不包括外部三方依赖。三方库的使用方式请参见相关章节。

5.4.1 架构路径定义

通过YAML配置文件指定不同架构的SDK路径:

x86_64架构: /sdk/dev_tools/sdk-buildchain/projects/sdk_defs/x86_64.yaml

python
sdk:
  local:
    path: /sdk/dev_tools/x86_64/

RK3588架构:/sdk/dev_tools/sdk-buildchain/projects/sdk_defs/rk3588_aarch64.yaml

python
sdk:
  local:
    path: /sdk/dev_tools/rk3588_aarch64/

5.4.2 SDK仓结构

plain
/sdk/dev_tools/{x86_64|rk3588_aarch64}/
    ├── aimdk/                              # Protobuf协议生成的头文件目录
    ├── aimrte/                             # HDS头文件目录
    ├── all_plugins/                        # 插件库目录(mqtt_plugin/ros2_plugin/net_plugin等)及其依赖
    ├── BUILD                               # SDK库的Bazel target定义文件
    ├── google/                             # Google Protobuf公共头文件(如any.pb.h)
    ├── libzy_aimrt_comm.so                 # aimrt_comm仓库API动态链接库
    ├── libzy_aimrt.so                      # aimrt仓库API动态链接库
    ├── ros2/                               # ROS2协议生成的头文件目录
    ├── src/                                # 核心头文件目录(aimrt/aimrt_comm/)
    └── WORKSPACE                           # SDK仓库的Bazel WORKSPACE配置文件

5.4.3 核心库说明

库名称功能描述
libaimrt运行时核心库,提供基础运行时功能(网络、配置等)
libaimrt_comm通信层库,包含Protobuf/ROS2协议及通用工具
libhealth_diag_generation健康诊断库,提供模块健康监控功能
all_plugins插件集合(mqtt_plugin/ros2_plugin等)

5.4.4 BUILD定义示例

python
# 导出所有SDK资源
package(default_visibility = ["//visibility:public"])
exports_files(glob(["**/*.h*", "**/*.inl", "**/*.so*"]))

# 核心库定义
cc_library(
    name = "libaimrt",
    includes = [
        "src/protocols/plugins/ros2_plugin_proto/common/msg",
    ],
    srcs = ["//:libzy_aimrt.so"],
    hdrs = [
        "src/common/net/asio_http_cli.h",
        "src/runtime/core/util/yaml_tools.h",
        # ...
    ],
    linkopts = [ 
    ],
    defines = [
        "BOOST_ASIO_NO_DEPRECATED", 
        "AIMRT_USE_FMT_LIB"
    ],
)

cc_library(
    name = "libaimrt_comm",
    includes = ["ros2/data_proxy", "ros2/audio/StopAudioPlay", ...],
    srcs = ["libzy_aimrt_comm.so"],
    hdrs = [
        "aimdk/protocol/action/arm.pb.h",
        "ros2/audio/audio_msgs/msg/volume_type.h", 
        "src/all_in_one/include/aimrte.h",
        # ...
    ],
    
    deps = [
        "@boost_x86_64//:boost",
        "@ros2_common_interfaces//:cpp_geometry_msgs",
        "@sdk//:libaimrt",
        "@sdk//:libhealth_diag_generation",
        # ...
    ],
    defines = ["AIMRT_USE_FMT_LIB"],
)
...

cc_library(
    name = "libhealth_diag_generation",
    srcs = [
        
    ],
    hdrs = glob(["aimrte/hds/**/*.h"]),
    deps = [
        
    ],
    linkopts = [
        
    ],
    defines = [
        
    ],
)

filegroup(
    name = "all_plugins",
    srcs = glob(["all_plugins/**"]),
)

cc_library(
    name = "agibot_sdk",
    deps = [
        ":libaimrt_comm",
    ],
)

5.4.5 业务仓引入方式

引入SDK依赖

通过 "//thirdparty:agibot_sdk"直接引用:

python
package(default_visibility = ["//visibility:public"])

cc_library(
    name = "aimdk_example_module",
    srcs = glob([
        "*.cpp",
    ]),
    hdrs = glob([
        "*.h",
    ]),
    deps = [
        "//thirdparty:agibot_sdk",
    ],
)

插件引入

在 pkg_tar 的 deps 中通过 @integration//:all_plugins_tar 打包:

python
pkg_tar(
    name = "aimdk_example_tar",
    srcs = [
    ],
    extension = "tar",
    mode = "0755",
    strip_prefix = ".",
    tags = ["tar"],
    include_runfiles = True,
    deps = [
        ...
        "@integration//:all_plugins_tar",
        ...
    ],
)

5.5 模块开发

5.5.1 AimRT 模块

AimRT 是面向机器人场景的通信与运行时框架。开发者把业务逻辑写成模块(Module),由应用(App)在进程内加载,通信方式(Channel 发布/订阅、RPC)、线程(Executor)、日志、插件等由 YAML 配置文件决定,业务代码与具体通信后端(iceoryx / ROS2 / HTTP 等)解耦。

本文以 aimdk_examples/examples/aimrt/aimdk_examples为例,说明如何基于 AimDK 使用 AimRT 框架编写、编译、运行一个自定义模块。

目录结构

plain
examples/aimrt/aimdk_examples/
├── app/
│   └── aimdk_example/
│       ├── main.cpp                       # 程序入口,注册模块
│       └── BUILD                          # 可执行文件与打包规则
├── module/
│   └── aimdk_example/
│       ├── aimdk_example.h                # 模块声明
│       ├── aimdk_example.cpp              # 模块实现
│       └── BUILD                          # 模块库(cc_library)
├── config/
│   └── aimdk_example/
│       ├── aimdk_example_config.yaml      # AimRT 运行配置
│       └── BUILD
└── scripts/
    └── aimdk_example/
        ├── start_aimdk_example.sh         # 启动脚本
        └── BUILD

仓库根目录的 BUILD 中 aimdk_example_tar 负责把配置、插件、可执行文件和启动脚本打成最终安装包 bazel-bin/aimdk_example_tar.tar。

examples/native/ 下是不依赖 AimRT 模块框架的原生 C++ / Python 示例,与本文无关。

5.5.1.1 编写模块 :

模块头文件(module/aimdk_example/aimdk_example.h)

c++
#pragma once#include "audio_msgs/msg/audio_capture.hpp"#include "src/all_in_one/include/aimrte.h"namespace aimdk_examples::module::aimdk_example {

class AimdkExampleModule : public aimrte::ctx::ModuleBase {
 public:
  struct Option {
    aimrte::Sub<audio_msgs::msg::AudioCapture> audio_capture_sub{"/audiohal/audio/capture"};
  };

 public:
  AimdkExampleModule();
  ~AimdkExampleModule() override;

  bool OnInitialize() override;
  bool OnStart() override;
  void OnShutdown() override;

  void ReceiveAudioCapture(const std::shared_ptr<const audio_msgs::msg::AudioCapture>& msg);

 private:
  Option option_;
};

}  // namespace aimdk_examples::module::aimdk_example

要点:

  • 只需引入 src/all_in_one/include/aimrte.h,即可使用 AimRT 封装好的 aimrte:: 接口。
  • 消息类型来自 AimDK 协议包(此处使用 ROS2 消息 audio_msgs::msg::AudioCapture)。
  • 把订阅者等资源集中放在 Option 结构体中:
    • aimrte::Sub<T>:某个 topic 的订阅句柄;
    • 如需在独立线程池中调度回调,可仿照 aimrte::Exe 声明执行器。

模块实现(module/aimdk_example/aimdk_example.cpp)

c++
bool AimdkExampleModule::OnInitialize() {
  option_.audio_capture_sub = aimrte::ctx::init::Subscriber<audio_msgs::msg::AudioCapture>("/audiohal/audio/capture");
  option_.audio_capture_sub.WhenInit().SubscribeInline(std::bind(&AimdkExampleModule::ReceiveAudioCapture, this, std::placeholders::_1));
  return true;
}

bool AimdkExampleModule::OnStart()    { return true; }
void AimdkExampleModule::OnShutdown() {}

void AimdkExampleModule::ReceiveAudioCapture(const std::shared_ptr<const audio_msgs::msg::AudioCapture>& msg) {
  AIMRTE_INFO("stamps: sec={}, nanosec={}", msg->stamps.sec, msg->stamps.nanosec);
  AIMRTE_INFO("mic_channels: {}", msg->mic_channels);
  AIMRTE_INFO("ref_channels: {}", msg->ref_channels);
  AIMRTE_INFO("info: channels={}, sample_rate={}, sample_format={}, coding_format={}",
              msg->info.channels, msg->info.sample_rate, msg->info.sample_format, msg->info.coding_format);
  AIMRTE_INFO("data: {} bytes", msg->data.data.size());
}

模块生命周期:

回调时机典型用途
OnInitialize()初始化阶段创建订阅者/发布者/RPC 客户端与服务、读取配置;返回 false 表示初始化失败
OnStart()启动阶段启动业务循环、定时任务等
OnShutdown()停止阶段释放资源

订阅方式:SubscribeInline(callback) 表示回调直接在消息到达的线程内执行,适合轻量处理。如需在指定线程池中处理耗时逻辑,可在 Option 中声明一个 aimrte::Exe 执行器(并在配置文件 aimrt.executor 中声明同名执行器)来调度,具体接口请参考 AimRT 官方文档与 AimDK 头文件)。

日志使用 AIMRTE_INFO(...)({} 格式化风格),日志等级由配置文件 aimrt.log 控制。

5.5.1.2 模块的 BUILD(module/aimdk_example/BUILD)

python
cc_library(
    name = "aimdk_example_module",
    srcs = glob(["*.cpp"]),
    hdrs = glob(["*.h"]),
    deps = [
        "//thirdparty:agibot_sdk",   # AimDK / AimRT 头文件与库
    ],
)

//thirdparty:agibot_sdk 提供了 AimRT 与 AimDK 协议的依赖,模块只需依赖它即可。

5.5.1.3 编写应用入口(app/aimdk_example/main.cpp)

c++
#include <iostream>#include "examples/aimrt/aimdk_examples/module/aimdk_example/aimdk_example.h"int main(int argc, char** argv) {
  std::cout << "Hello, AIMDK!" << std::endl;

  aimrte::Cfg cfg(argc, argv, "aimdk_example");
  return aimrte::Run(cfg, {{"AimdkExampleModule", std::make_shared<aimdk_examples::module::aimdk_example::AimdkExampleModule>()}});
}
  • aimrte::Cfg 解析命令行参数(启动脚本传入的 --cfg_file_path=...)。
  • aimrte::Run 接收模块名到模块实例的映射并运行;"AimdkExampleModule" 是模块名,需要与配置文件中对该模块的配置保持一致。
  • 需要多个模块时,在映射中继续添加即可。

app/aimdk_example/BUILD 中的 cc_binary 依赖各模块库,并把可执行文件、配置、启动脚本打成安装包:

python
cc_binary(
    name = "aimdk_example",
    srcs = ["main.cpp"],
    deps = ["//examples/aimrt/aimdk_examples/module/aimdk_example:aimdk_example_module"],
)

5.5.1.4 配置文件(config/aimdk_example/aimdk_example_config.yaml)

配置文件决定运行时行为,主要段落:

段落作用本例
aimrt.configurator配置器,temp_cfg_path 为临时模块配置文件的存放路径../config/aimdk_example/tmp
aimrt.plugin加载插件net_plugin、ros2_plugin、iceoryx_plugin
aimrt.log日志等级core_lvl: INFO,default_module_lvl: Info
aimrt.executor执行器(线程池)work_thread_pool(4)、timeout_handle(2)
aimrt.channelChannel 后端与 topic 选项后端 iceoryx、ros2;订阅 /audiohal/audio/capture 时只启用 ros2(audiohal 通过 ROS2 发布该 topic,与原生示例一致)
aimrt.rpcRPC 后端http、ros2

代码订阅的 topic 与后端需要与 channel 段匹配;如新增执行器,执行器名也必须先在 aimrt.executor 中声明,代码里才能通过 aimrte::Exe{"名字"} 引用。

5.5.1.5 启动脚本与打包

启动脚本为 scripts/aimdk_example/start_aimdk_example.sh,启动时通过 --cfg_file_path 传入。

5.5.1.6 编译与运行

编译、本地运行、部署到设备、调试的完整步骤见 编译与部署,本例的目标名为 aimdk_example:

bash
bash tools/build_module.sh -t aimdk_example -a x86_64
bash tools/run_module.sh -t aimdk_example

运行成功后,模块会在收到 /audiohal/audio/capture 消息时输出时间戳、麦克风与参考通道数、采样信息和数据长度的日志。

5.5.1.7 新增自己的模块

以新增模块 foo 为例:

  1. 创建模块:新建 module/foo/foo.h、foo.cpp,继承 aimrte::ctx::ModuleBase 并实现 OnInitialize / OnStart / OnShutdown。
  2. 模块 BUILD:仿照 module/aimdk_example/BUILD 新增 cc_library(如 foo_module),依赖 //thirdparty:agibot_sdk。
  3. 注册模块:在 app/aimdk_example/BUILD 的 cc_binary.deps 中加入 //examples/aimrt/aimdk_examples/module/foo:foo_module(或参照该 BUILD 新建自己的 app/foo/BUILD);在 main.cpp 中 #include 头文件,并在 aimrte::Run 的映射里增加 {"FooModule", std::make_shared<...::Foo>()}。
  4. 更新配置:如用到新的执行器、topic 后端选项、插件,在 YAML 对应段落补充。
  5. 编译运行

5.5.1.8 常用通信写法提示

  • 发布消息 / RPC:接口同样通过 aimrte::ctx::init:: 在 OnInitialize 中创建,具体 API 请参考 AimRT 官方教程中 Channel、RPC 章节以及 src/all_in_one/include/aimrte.h。
  • 不使用 AimRT 模块框架的调用方式(直接调 topic/RPC 的独立程序),参考 examples/native/cpp 与 examples/native/python。

5.5.1.9 常见问题

  • 启动即退出 / 初始化失败:检查 OnInitialize 是否返回 true,以及代码中使用的执行器、topic 是否已在 YAML 中配置。
  • 收不到消息:确认发布端与订阅端使用同一 topic、同一后端(本例订阅端仅启用 ros2),且设备上 audiohal 相应服务已运行。
  • 调整日志:修改配置中 aimrt.log.core_lvl / default_module_lvl。
  • 调试:使用 start_aimdk_example.sh -g -p <port> 以 lldb-server 启动,再远程连接。

5.5.2 原生 C++ 模块开发

本文以aimdk_examples/examples/native/cpp目录示例,不使用 AimRT 模块框架,而是作为独立进程,直接通过 ROS2 或 HTTP 与机器人服务交互。每个示例是一个独立的 cc_binary,可单独编译、打包、运行。

5.5.2.1 目录结构

每个示例目录结构一致:

plain
cpp/
├── channel/<name>/
│   ├── <name>.cpp             # 示例源码
│   ├── start_<name>.sh        # 启动脚本
│   └── BUILD                  # 可执行文件与打包规则
└── rpc/<name>/
    ├── <name>.cpp
    ├── start_<name>.sh
    └── BUILD

5.5.2.2 Channel 示例

订阅 protobuf 消息:heart_beat、tts_status

这两个示例结构相同,仅 topic 与消息类型不同:

示例Topicprotobuf 类型
heart_beat/aima/heartbeat/pb_3Aaimdk_2Eprotocol_2EProcessHeartbeatChannelaimdk::protocol::ProcessHeartbeatChannel
tts_status/interaction/tts_status/pb_3Aaimdk_2Eprotocol_2ETTSStatusaimdk::protocol::TTSStatus
要点:
  • 用 rclcpp::Node 订阅 ros2_plugin_proto::msg::RosMsgWrapper。该消息是 AimRT ROS2 插件对 protobuf 的封装,serialization_type 为 "pb" 时,data 中是 protobuf 序列化后的字节。
  • 回调里先判断 serialization_type == "pb",再用 ParseFromArray 反序列化为具体的 protobuf 消息,最后用 MessageToJsonString 转成 JSON 输出。
  • topic 名中的 pb_3Aaimdk_2Eprotocol_2E... 是 protobuf 类型全名(pb:aimdk.protocol.…)经 ROS2 名称转义后的结果(: → _3A,. → _2E)。
  • QoS 使用 KeepLast(10) + best_effort。
c++
rclcpp::QoS qos_profile(rclcpp::KeepLast(10));
qos_profile.best_effort();
subscription_ = create_subscription<ros2_plugin_proto::msg::RosMsgWrapper>(
    topic_name_, qos_profile, std::bind(&Xxx::Callback, this, std::placeholders::_1));

5.5.2.3 订阅 ROS2 消息

订阅 ROS2 消息:joint_state、audio_capture

示例Topic消息类型消息来源
joint_state/motion/control/arm_joint_statesensor_msgs::msg::JointStateROS2 标准消息
audio_capture/audiohal/audio/captureaudio_msgs::msg::AudioCapture
AimDK 自定义 ROS2 消息
直接订阅 ROS2 消息类型,回调中按字段打印(joint_state 打印 header / name / position / velocity / effort;audio_capture 打印时间戳、麦克风与参考通道数、采样信息与数据长度)。

audio_msgs 不是 ROS2 自带的包,而是 AimDK 定义的消息包,需要运行环境中已加载 AimDK 的 ROS2 消息包(启动脚本会加载 /agibot/software/v0/entry/env/env.sh)。同属该包的 audio_msgs::srv::AbandonAudioFocus 也是自定义服务。

5.5.2.4 RPC 示例

HTTP + JSON:GetArmJointState、GetAction

示例URL请求 / 响应类型
GetArmJointStatehttp://10.42.10.12:56322/rpc/aimdk.protocol.MotionControlJointService/GetArmJointStateCommonRequest / JointStateResponse
GetActionhttp://10.42.10.12:56322/rpc/aimdk.protocol.MotionControlActionService/GetAction
CommonRequest / MotionControlActionResponse

这两个示例通过 libcurl 向机器人服务的 HTTP RPC 接口发送 POST 请求:

URL 规则:http://<设备IP>:<端口>/rpc/<protobuf 服务全名>/<方法名>。

调用流程(各示例一致):

  1. 构造 protobuf 请求,用 MessageToJsonString 转成 JSON;
  2. curl 以 Content-Type: application/json POST 到对应 URL,超时 5 秒;
  3. 检查 curl 返回码和 HTTP 状态码(须为 200);
  4. 用 JsonStringToMessage(ignore_unknown_fields = true)解析响应,并格式化输出。

任一步失败时向 stderr 打印错误并以退出码 1 结束。

SetAudioVolume 需要一个音量参数,请求中固定 is_mute = false、type = SPEAKRE_BUILT_IN(内置扬声器):

bash
./SetAudioVolume 50

代码中的设备 IP(10.42.10.12、10.42.10.10)是硬编码的,如设备网络不同需要修改源码中的 kUrl 后重新编译。

5.5.2.5 ROS2 Service:AbandonAudioFocus

  • 服务名:/audio_5Fmsgs/srv/AbandonAudioFocus(AimDK 自定义服务 audio_msgs::srv::AbandonAudioFocus,_5F 是 _ 的转义)。
  • 流程:创建 client → 每 2 秒循环 wait_for_service 直到服务可用 → 填充 focus_requester(pkg_name = "aimdk_examples"、priority = 6、priority_weight = 1)→ async_send_request → spin_until_future_complete → 打印 pkg_name 与 focus_gain。
  • 退出时使用 std::_Exit 直接结束进程,不做 rclcpp::shutdown 和对象析构。源码注释说明原因:rclcpp::Node 析构触发的 DDS participant 销毁,与 agibot_sdk 自带 libzy_aimrt.so 内部的 asio scheduler 存在符号冲突,会在退出阶段 segfault。这只适用于这类一次性调用工具,常驻程序不要照搬。

5.5.2.6 BUILD 依赖

示例类型依赖
Channel(4 个)//thirdparty:cpp_rclcpp;heart_beat、tts_status 另有 //thirdparty:protobuf、//thirdparty:agibot_sdk
HTTP RPC(4 个)//thirdparty:CURL、//thirdparty:protobuf、//thirdparty:agibot_sdk
AbandonAudioFocus//thirdparty:cpp_rclcpp、//thirdparty:agibot_sdk

每个 BUILD 除 cc_binary 外,还包含三个打包目标:

  • <name>_script:把 start_<name>.sh 放到 scripts/<name>/;
  • <name>_without_bin_tar:仅含脚本;
  • <name>_bin_flatten_tar:可执行文件及依赖,放到 bin/。

根目录 BUILD 中的 <name>_tar 把这两部分合并成最终安装包。

5.5.2.7 编译

build_module.sh -t <name> 会构建 <name>_tar,因此 -t 直接传示例名:

bash
# x86
bash tools/build_module.sh -t heart_beat -a x86_64

# ARM
bash tools/build_module.sh -t heart_beat -a rk3588_mdu_aarch64
bash tools/build_module.sh -t heart_beat -a rk3588_hdu_aarch64

可用的 -t 值:heart_beat、tts_status、joint_state、audio_capture、GetArmJointState、GetAction、GetAudioVolume、SetAudioVolume、AbandonAudioFocus。

产物为 bazel-bin/<name>_tar.tar。

5.5.2.8 运行

本地(x86,aimde 环境)

bash
bash tools/run_module.sh -t heart_beat

设备上

传输并解压安装包后,使用启动脚本:

bash
bash /agibot/data/ggm/scripts/heart_beat/start_heart_beat.sh

启动脚本会进入 ../../bin,加载 /agibot/software/v0/entry/env/env.sh(或 /opt/ros/jazzy、/opt/ros/humble)环境,然后启动可执行文件,并处理 Ctrl+C(SIGINT/SIGTERM)优雅退出。

通用参数:

参数说明
-g, --debug以 lldb-server 调试模式运行
-p, --port调试端口(默认 65001)
-h, --help帮助

start_SetAudioVolume.sh 另需 -v, --volume <音量>(必填):

bash
bash scripts/SetAudioVolume/start_SetAudioVolume.sh -v 50

5.6 编译与部署

本文是 AimRT 模块和原生 C++ 示例共用的编译、本地运行、部署流程。二者都通过同一套脚本完成,区别只在于 -t 传入的目标名。

目标名 tools/build_module.sh -t 会构建根目录 BUILD 中的 _tar,产物为 bazel-bin/_tar.tar。

类别可用的 <name>
AimRT 模块aimdk_example
原生 C++ Channelheart_beat、tts_status、joint_state、audio_capture
原生 C++ RPCGetArmJointState、GetAction、GetAudioVolume、SetAudioVolume、AbandonAudioFocus

tools/build_module.sh -t <name> 会构建根目录 BUILD 中的 <name>_tar,产物为 bazel-bin/<name>_tar.tar。

下文以 aimdk_example 为例,换成其他目标名即可。

5.6.1 编译

根据目标设备选择架构:

bash
# ARM - mdu
bash tools/build_module.sh -t aimdk_example -a rk3588_mdu_aarch64

# ARM - hdu
bash tools/build_module.sh -t aimdk_example -a rk3588_hdu_aarch64

# x86
bash tools/build_module.sh -t aimdk_example -a x86_64

编译产物:bazel-bin/aimdk_example_tar.tar

build_module.sh 参数:

参数说明
-t目标名(必填)
-a架构:x86_64(默认)、rk3588_mdu_aarch64、rk3588_hdu_aarch64
-j并发编译任务数
-g调试编译(--copt=-g --copt=-O0)
-c编译前先 bazel clean --expunge
-e追加 bazel 选项,如 -e "--config=xxx"

本地运行(x86)

x86 编译产物可直接在 aimde 环境中运行:

bash
bash tools/run_module.sh -t aimdk_example

run_module.sh 会把 bazel-bin/<name>_tar.tar 解压到 bazel-bin/<name>_tar/,再执行其中的 scripts/<name>/start_<name>.sh。

参数:

参数说明
-t目标名(必填),需先完成 x86 编译
-c自定义配置文件绝对路径,仅 AimRT 模块可用(原生示例的启动脚本不接受 -c)
-g调试模式
-p调试端口(-g 时生效)

例:bash tools/run_module.sh -t aimdk_example -c $(pwd)/examples/aimrt/aimdk_examples/config/aimdk_example/aimdk_example_config.yaml

run_module.sh 不会转发示例自身的参数。SetAudioVolume 需要 -v <音量>,请进入解压目录直接运行启动脚本:

bash
bash bazel-bin/SetAudioVolume_tar/scripts/SetAudioVolume/start_SetAudioVolume.sh -v 50

5.6.2 部署到目标设备

部署前请确保已使用目标设备对应的架构(rk3588_mdu_aarch64 或 rk3588_hdu_aarch64)完成编译。

以下 IP、账号、目录取自示例环境,请按实际设备替换。

5.6.2.1 传输安装包

bash
sshpass -p 1 scp \
  -o UserKnownHostsFile=/dev/null \
  -o StrictHostKeyChecking=no \
  -o ConnectTimeout=120 \
  -P 22 \
  bazel-bin/aimdk_example_tar.tar \
  agi@192.168.111.140:/agibot/data/ggm/

5.6.2.2 登录设备并解压

bash
ssh agi@192.168.111.140 -p 22
bash
cd /agibot/data/ggm
tar -xvf aimdk_example_tar.tar
rm aimdk_example_tar.tar

5.6.2.3 启动

bash
bash /agibot/data/ggm/scripts/aimdk_example/start_aimdk_example.sh

5.6.2.4 安装包布局与启动脚本

解压后的目录(以 AimRT 模块为例):

plain
bin/                              # 可执行文件、依赖库;AimRT 模块还包含插件
config/aimdk_example/             # 配置文件(仅 AimRT 模块)
scripts/aimdk_example/            # 启动脚本

原生 C++ 示例的安装包只有 bin/ 和 scripts/<name>/,没有 config/。

启动脚本 scripts/<name>/start_<name>.sh 的行为:

  1. 进入 ../../bin;
  2. 加载环境:优先 /agibot/software/v0/entry/env/env.sh,其次 /opt/ros/jazzy、/opt/ros/humble;
  3. 启动可执行文件;AimRT 模块额外传入 --cfg_file_path=<配置文件>;
  4. 捕获 SIGINT / SIGTERM 并优雅退出(最多等待 30 秒,超时强杀)。

启动脚本参数:

参数适用说明
-c, --config仅 AimRT 模块自定义配置文件(绝对路径),默认 ../config/aimdk_example/aimdk_example_config.yaml
-g, --debug全部以 lldb-server 调试模式运行
-p, --port全部调试端口,默认 65001
-h, --help全部帮助

5.6.2.5 调试

  1. 用 -g 编译(带调试符号、关闭优化):
bash
bash tools/build_module.sh -t aimdk_example -a rk3588_mdu_aarch64 -g
  1. 部署后以调试模式启动,由 lldb-server 监听端口:
bash
bash /agibot/data/ggm/scripts/aimdk_example/start_aimdk_example.sh -g -p 65001
  1. 在开发机上用 lldb 远程连接该端口。

5.6.2.6 常见问题

  • Target tar does not exist:运行前没有完成对应目标的 x86 编译,先执行 build_module.sh -t <name> -a x86_64。
  • 部署后无法运行 / 架构不匹配:确认编译时 -a 与设备型号(mdu / hdu)一致,不能用 x86 产物部署到设备。
  • Unsupported ARCH_TYPE:-a 只支持 x86_64、rk3588_mdu_aarch64、rk3588_hdu_aarch64。
  • 原生示例传 -c 报错:-c 只适用于 AimRT 模块。

5.7 python 编译与部署

本文以aimdk_examples/examples/native/python目录下作示例,同样不使用 AimRT 模块框架,作为独立脚本通过 ROS2(rclpy)或 HTTP(requests)与机器人服务交互。Python 示例不经 Bazel 编译,直接用 Python 解释器运行。

5.7.1 目录结构

plain
python/
├── pyproject.toml             # 项目依赖声明(aimdk-proto、protobuf、requests,见 3.3)
├── uv.lock                    # 依赖锁文件(唯一事实来源)
├── requirements.txt           # 由 uv.lock 导出的中间产物
├── .venv-x86/ .venv-rk3588/   # 各芯片的虚拟环境(SDK 发布前已构建好)
├── channel/
│   └── <name>/{<name>.py, start_<name>.sh}
└── rpc/
    └── <name>/{<name>.py, start_<name>.sh}

5.7.2 Channel 示例

订阅 protobuf 消息:heart_beat、tts_status

示例Topicprotobuf 类型
heart_beat/aima/heartbeat/pb_3Aaimdk_2Eprotocol_2EProcessHeartbeatChannelaimdk::protocol::ProcessHeartbeatChannel
tts_status/interaction/tts_status/pb_3Aaimdk_2Eprotocol_2ETTSStatusaimdk::protocol::TTSStatus
要点:
  • 脚本直接 from aimdk.protocol.xxx import xxx_pb2 导入,这些模块已经安装在虚拟环境中,无需任何 sys.path 处理。
  • 订阅 ros2_plugin_proto.msg.RosMsgWrapper;回调中先检查 serialization_type == "pb",再用 ParseFromString(b"".join(msg.data)) 反序列化,最后用 MessageToJson 输出。
  • QoS:KEEP_LAST、深度 10、BEST_EFFORT。
  • topic 名是 protobuf 类型全名经 ROS2 名称转义的结果(: → _3A,. → _2E)。

订阅自定义 ROS2 消息:audio_capture

订阅 /audiohal/audio/capture(AimDK 自定义 ROS2 消息 audio_msgs.msg.AudioCapture,非 ROS2 标准消息),打印时间戳、麦克风与参考通道数、采样信息和数据长度。

四个 Channel 脚本都用 try/except (KeyboardInterrupt, ExternalShutdownException) 处理 Ctrl+C,并在 finally 中 destroy_node 与 rclpy.shutdown()。

aimdk.protocol.* protobuf 绑定(aimdk-proto 包)

heart_beat、tts_status 用到的 aimdk.protocol.*_pb2 模块,由 SDK 提供的 aimdk-proto 包给出。它已经声明在 pyproject.toml 依赖里,构建虚拟环境时会自动装好,写代码时直接 import 即可,不需要额外配置。

模块路径规则:aimdk.protocol.<子目录...>.<文件名>_pb2,与 AimDK 协议里对应 .proto 文件的目录结构一一对应,例如:

protobuf 类型导入方式
ProcessHeartbeatChannelfrom aimdk.protocol.hds.process import process_heartbeat_channel_pb2
TTSStatusfrom aimdk.protocol.interaction import tts_service_pb2

用到其他消息时按同样规则导入即可。

5.7.3 RPC 示例

示例URL
GetArmJointStatehttp://10.42.10.12:56322/rpc/aimdk.protocol.MotionControlJointService/GetArmJointState
GetActionhttp://10.42.10.12:56322/rpc/aimdk.protocol.MotionControlActionService/GetAction

使用 requests.post(URL, json=payload, timeout=5) 调用机器人 HTTP RPC 接口,成功后打印格式化 JSON:

  • URL 规则:http://<设备IP>:<端口>/rpc/<protobuf 服务全名>/<方法名>。
  • 查询类接口请求体为空对象 {};SetAudioVolume 请求体为:
python
{"audio_volume": int(sys.argv[1]), "is_mute": False, "type": "SPEAKRE_BUILT_IN"}
  • 请求异常或 HTTP 状态码非 2xx(raise_for_status)时,向 stderr 输出错误并返回退出码 1。
  • Python 版直接使用 JSON 字典,不依赖 protobuf 生成代码;与 C++ 版相同,设备 IP 是硬编码的,网络不同时需修改 URL。

ROS2 Service:AbandonAudioFocus

  • 服务名 /audio_5Fmsgs/srv/AbandonAudioFocus(AimDK 自定义服务 audio_msgs.srv.AbandonAudioFocus)。
  • 流程:创建 client → 每 2 秒 wait_for_service 直到可用 → 填充 focus_requester(pkg_name = "aimdk_examples"、priority = 6、priority_weight = 1)→ call_async → spin_until_future_complete → 打印 pkg_name 与 focus_gain。
  • 与 C++ 版不同,Python 版不需要 _Exit 规避退出崩溃,正常 destroy_node + shutdown 即可。

5.7.4 Python 环境需求

依赖(pyproject.toml,要求 Python ≥ 3.10):

  • aimdk-proto(AimDK 协议的 protobuf Python 绑定,随 SDK 环境提供)
  • protobuf>=6.31,<7(不能低于 aimdk-proto 生成代码所用的版本)
  • requests>=2.34.2

rclpy、sensor_msgs、audio_msgs、ros2_plugin_proto 等 ROS2 包不在该依赖中,而是来自系统 ROS2 / AimDK 环境(虚拟环境以 --system-site-packages 创建,复用系统包)。

各芯片的虚拟环境(.venv-x86、.venv-rk3588)已经随 SDK 构建好,直接使用即可,不需要自己安装依赖。

5.7.5 打包运行环境

bash
bash tools/package_python_env.sh -c rk3588
# 默认输出 dist/aimdk_examples_python_env-rk3588.tar.gz,可用 -o 指定

打包时会复制源码到暂存目录,只保留目标芯片的虚拟环境并改名为 .venv(因此启动脚本里统一使用 .venv/bin/python3),同时清理 pycache 等文件,并删除 shebang 写死本机路径的失效脚本。

5.7.6 运行

5.7.6.1 本地

在具备 ROS2 环境的机器上,进入示例目录,用启动脚本运行:

bash
cd examples/native/python/channel/heart_beat
bash start_heart_beat.sh

RPC 示例:

bash
cd examples/native/python/rpc/SetAudioVolume
bash start_SetAudioVolume.sh -v 50

启动脚本的行为:

  1. 加载 ROS2 环境:优先 /agibot/software/v0/entry/env/env.sh,其次 /opt/ros/jazzy、/opt/ros/humble;
  2. 对用到 AimDK 消息的示例,额外 source 对应的 local_setup.bash:heart_beat、tts_status 加载 ros2_plugin_proto,audio_capture、AbandonAudioFocus 加载 audio_msgs;
  3. 优先使用 python/.venv/bin/python3,找不到则回退到系统 python3;
  4. 运行 .py 脚本,并处理 SIGINT/SIGTERM 优雅退出(最多等待 30 秒)。

start_SetAudioVolume.sh 需要 -v, --volume <音量>(必填)。其他脚本仅支持 -h。

5.7.6.2 部署到设备

  1. 打包对应芯片的运行环境,得到 aimdk_examples_python_env-<chip>.tar.gz;
  2. 拷贝到设备并解压:tar -xzf aimdk_examples_python_env-rk3588.tar.gz,得到 python/ 目录;
  3. 进入 python/channel/<name> 或 python/rpc/<name>,运行 start_<name>.sh。