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.执行安装脚本,安装开发环境
./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/ 路径。
# 在主机上执行
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)预编译库,用于支持项目的编译、开发与部署。
/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平台的交叉编译工具链和部分三方依赖库。
/opt/rockship
├── rk3588_sysroot # ARM架构的三方库
└── rk3588-toolchain # 交叉编译工具链
/opt/nvidia
提供Nvidia Thor平台的交叉编译工具链和部分三方依赖库。
/opt/rockship
├── thor_sysroot # ARM架构的三方库
└── thor-toolchain # 交叉编译工具链
/workspaces/aimde目录
开发工作区,包含 AimDK 通信协议、示例代码及开发容器配置。其中 aima_protocol 和 aimdk_examples 由 setup-init 从 SDK 同步而来,可在此直接修改、编译和调试。
/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 完整镜像更新
- 更新镜像版本配置
编辑 .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 包更新
- 复制 DevKit 包到挂载目录
在AimDK下载中心获取AimDK-A3-v3.3-DevKit压缩包,将 DevKit 包复制到主机的挂载目录 ~/sdk/a3/aimde/,该目录会自动映射到容器内的 /workspaces/aimde/ 路径。
# 在主机上执行
cp *sdk-package-a3-v3.3.13-0-gb889a630d-2610080917-devkit.tar.gz ~/sdk/a3/aimde/
- 在容器内替换 DevKit 包
进入 DevContainer 终端,执行以下命令:
# 清理旧版本
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. 删除旧版本目录
rm -rf /workspaces/aimde/aima_protocol /workspaces/aimde/aimdk_examples
⚠️ 此操作会删除这两个目录下的所有本地修改。如有改动需要保留,请先备份或提交到版本库。
2. 重新初始化工作区
setup-init
setup-init 会从 SDK 重新拉取最新的 aima_protocol 和 aimdk_examples 到工作区。
5.2.6 注意事项
- 更新前建议备份重要数据和配置文件
- 确保 Docker 服务正常运行
- 完整镜像更新会重建容器,容器内未持久化的数据将丢失
- DevKit 更新仅替换 SDK 工具包,不影响容器配置
5.3 第三方库管理
构建框架依赖的常用三方库已预先集成在本地开发环境(aimde)中,通过 Bazel rule 统一管理,用户只需在业务代码中按规范引入即可,无需自行下载和安装。同时,框架也支持用户按需自行添加三方库,并以相同的方式进行管理和引用。
5.3.1 三方库定义链条
三方库从本地文件到业务代码,经过三层定义,每层职责单一:
/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结尾
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 按目标平台自动切换:
# 预编译:按架构选择
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 下再做一层转发,本仓所用的三方库一目了然:
alias(
name = "boost",
actual = "@integration//:boost",
)
alias(
name = "jsoncpp",
actual = "@integration//:jsoncpp",
)
业务代码只依赖 //thirdparty:xxx:
cc_binary(
name = "demo",
srcs = ["main.cc"],
deps = [
"//thirdparty:boost",
"//thirdparty:jsoncpp",
],
)
5.3.5 新增三方库
- 将库文件放入
/sdk/dev_tools/third_party_libs/external/(预编译库按架构分目录) - 在
sdk_third_party_deps.bzl中用local_repository注册 - 在
integration/BUILD中添加alias(预编译库使用select) - 在业务仓
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
sdk:
local:
path: /sdk/dev_tools/x86_64/
RK3588架构:/sdk/dev_tools/sdk-buildchain/projects/sdk_defs/rk3588_aarch64.yaml
sdk:
local:
path: /sdk/dev_tools/rk3588_aarch64/
5.4.2 SDK仓结构
/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定义示例
# 导出所有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"直接引用:
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 打包:
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 框架编写、编译、运行一个自定义模块。
- AimRT 官方教程:https://docs.aimrt.org/tutorials/index.html
- AimRT 源码:https://github.com/AimRT/AimRT
目录结构
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)
#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)
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)
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)
#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 依赖各模块库,并把可执行文件、配置、启动脚本打成安装包:
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.channel | Channel 后端与 topic 选项 | 后端 iceoryx、ros2;订阅 /audiohal/audio/capture 时只启用 ros2(audiohal 通过 ROS2 发布该 topic,与原生示例一致) |
| aimrt.rpc | RPC 后端 | 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 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 为例:
- 创建模块:新建
module/foo/foo.h、foo.cpp,继承aimrte::ctx::ModuleBase并实现OnInitialize/OnStart/OnShutdown。 - 模块 BUILD:仿照
module/aimdk_example/BUILD新增cc_library(如foo_module),依赖//thirdparty:agibot_sdk。 - 注册模块:在
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>()}。 - 更新配置:如用到新的执行器、topic 后端选项、插件,在 YAML 对应段落补充。
- 编译运行
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 目录结构
每个示例目录结构一致:
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 与消息类型不同:
| 示例 | Topic | protobuf 类型 |
|---|---|---|
| heart_beat | /aima/heartbeat/pb_3Aaimdk_2Eprotocol_2EProcessHeartbeatChannel | aimdk::protocol::ProcessHeartbeatChannel |
| tts_status | /interaction/tts_status/pb_3Aaimdk_2Eprotocol_2ETTSStatus | aimdk::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。
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_state | sensor_msgs::msg::JointState | ROS2 标准消息 |
| audio_capture | /audiohal/audio/capture | audio_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 | 请求 / 响应类型 |
|---|---|---|
| GetArmJointState | http://10.42.10.12:56322/rpc/aimdk.protocol.MotionControlJointService/GetArmJointState | CommonRequest / JointStateResponse |
| GetAction | http://10.42.10.12:56322/rpc/aimdk.protocol.MotionControlActionService/GetAction | CommonRequest / MotionControlActionResponse |
这两个示例通过 libcurl 向机器人服务的 HTTP RPC 接口发送 POST 请求:
URL 规则:http://<设备IP>:<端口>/rpc/<protobuf 服务全名>/<方法名>。
调用流程(各示例一致):
- 构造 protobuf 请求,用
MessageToJsonString转成 JSON; curl以Content-Type: application/jsonPOST 到对应 URL,超时 5 秒;- 检查 curl 返回码和 HTTP 状态码(须为 200);
- 用
JsonStringToMessage(ignore_unknown_fields = true)解析响应,并格式化输出。
任一步失败时向 stderr 打印错误并以退出码 1 结束。
SetAudioVolume 需要一个音量参数,请求中固定 is_mute = false、type = SPEAKRE_BUILT_IN(内置扬声器):
./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 直接传示例名:
# 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 tools/run_module.sh -t heart_beat
设备上
传输并解压安装包后,使用启动脚本:
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 scripts/SetAudioVolume/start_SetAudioVolume.sh -v 50
5.6 编译与部署
本文是 AimRT 模块和原生 C++ 示例共用的编译、本地运行、部署流程。二者都通过同一套脚本完成,区别只在于 -t 传入的目标名。
目标名
tools/build_module.sh -t
| 类别 | 可用的 <name> |
|---|---|
| AimRT 模块 | aimdk_example |
| 原生 C++ Channel | heart_beat、tts_status、joint_state、audio_capture |
| 原生 C++ RPC | GetArmJointState、GetAction、GetAudioVolume、SetAudioVolume、AbandonAudioFocus |
tools/build_module.sh -t <name> 会构建根目录 BUILD 中的 <name>_tar,产物为 bazel-bin/<name>_tar.tar。
下文以 aimdk_example 为例,换成其他目标名即可。
5.6.1 编译
根据目标设备选择架构:
# 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 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 <音量>,请进入解压目录直接运行启动脚本:bashbash 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 传输安装包
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 登录设备并解压
ssh agi@192.168.111.140 -p 22
cd /agibot/data/ggm
tar -xvf aimdk_example_tar.tar
rm aimdk_example_tar.tar
5.6.2.3 启动
bash /agibot/data/ggm/scripts/aimdk_example/start_aimdk_example.sh
5.6.2.4 安装包布局与启动脚本
解压后的目录(以 AimRT 模块为例):
bin/ # 可执行文件、依赖库;AimRT 模块还包含插件
config/aimdk_example/ # 配置文件(仅 AimRT 模块)
scripts/aimdk_example/ # 启动脚本
原生 C++ 示例的安装包只有 bin/ 和 scripts/<name>/,没有 config/。
启动脚本 scripts/<name>/start_<name>.sh 的行为:
- 进入
../../bin; - 加载环境:优先
/agibot/software/v0/entry/env/env.sh,其次/opt/ros/jazzy、/opt/ros/humble; - 启动可执行文件;AimRT 模块额外传入
--cfg_file_path=<配置文件>; - 捕获 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 调试
- 用
-g编译(带调试符号、关闭优化):
bash tools/build_module.sh -t aimdk_example -a rk3588_mdu_aarch64 -g
- 部署后以调试模式启动,由 lldb-server 监听端口:
bash /agibot/data/ggm/scripts/aimdk_example/start_aimdk_example.sh -g -p 65001
- 在开发机上用 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 目录结构
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
| 示例 | Topic | protobuf 类型 |
|---|---|---|
| heart_beat | /aima/heartbeat/pb_3Aaimdk_2Eprotocol_2EProcessHeartbeatChannel | aimdk::protocol::ProcessHeartbeatChannel |
| tts_status | /interaction/tts_status/pb_3Aaimdk_2Eprotocol_2ETTSStatus | aimdk::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 类型 | 导入方式 |
|---|---|
| ProcessHeartbeatChannel | from aimdk.protocol.hds.process import process_heartbeat_channel_pb2 |
| TTSStatus | from aimdk.protocol.interaction import tts_service_pb2 |
用到其他消息时按同样规则导入即可。
5.7.3 RPC 示例
| 示例 | URL |
|---|---|
| GetArmJointState | http://10.42.10.12:56322/rpc/aimdk.protocol.MotionControlJointService/GetArmJointState |
| GetAction | http://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请求体为:
{"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 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 环境的机器上,进入示例目录,用启动脚本运行:
cd examples/native/python/channel/heart_beat
bash start_heart_beat.sh
RPC 示例:
cd examples/native/python/rpc/SetAudioVolume
bash start_SetAudioVolume.sh -v 50
启动脚本的行为:
- 加载 ROS2 环境:优先
/agibot/software/v0/entry/env/env.sh,其次/opt/ros/jazzy、/opt/ros/humble; - 对用到 AimDK 消息的示例,额外
source对应的local_setup.bash:heart_beat、tts_status加载ros2_plugin_proto,audio_capture、AbandonAudioFocus加载audio_msgs; - 优先使用
python/.venv/bin/python3,找不到则回退到系统python3; - 运行
.py脚本,并处理 SIGINT/SIGTERM 优雅退出(最多等待 30 秒)。
start_SetAudioVolume.sh 需要 -v, --volume <音量>(必填)。其他脚本仅支持 -h。
5.7.6.2 部署到设备
- 打包对应芯片的运行环境,得到
aimdk_examples_python_env-<chip>.tar.gz; - 拷贝到设备并解压:
tar -xzf aimdk_examples_python_env-rk3588.tar.gz,得到python/目录; - 进入
python/channel/<name>或python/rpc/<name>,运行start_<name>.sh。