AimRT 从零到一:官方示例精讲 —— 二、HelloWorld示例.md
HelloWorld示例
官方仓库:helloworld
配置文件(configuration_helloworld.yaml
)
依据官方示例项目结构自行编写YAML配置文件
# 基础信息
base_info:project_name: helloworld # 项目名称build_mode_tags: ["EXAMPLE", "SIMULATION", "TEST_CAMERA"] # 构建模式标签aimrt_import_options: # AimRT框架的构建选项AIMRT_BUILD_TESTS: "OFF" # 是否构建测试代码AIMRT_BUILD_EXAMPLES: "ON" # 是否构建示例代码AIMRT_BUILD_DOCUMENT: "OFF" # 是否构建文档AIMRT_BUILD_RUNTIME: "ON" # 是否构建运行时核心AIMRT_BUILD_CLI_TOOLS: "OFF" # 是否构建命令行工具AIMRT_BUILD_WITH_PROTOBUF: "ON" # 是否启用Protobuf支持AIMRT_USE_LOCAL_PROTOC_COMPILER: "OFF" # 是否使用本地protoc编译器AIMRT_BUILD_WITH_ROS2: "OFF" # 是否集成ROS2支持AIMRT_BUILD_NET_PLUGIN: "OFF" # 是否构建网络插件AIMRT_BUILD_ROS2_PLUGIN: "OFF" # 是否构建ROS2插件# ... # 其他插件选项(MQTT、Zenoh等)# 模块 会生成 ./helloworld/src/module/helloworld_module/helloworld_module.h
modules:- name: HelloWorld_module # 自定义模块名称,同时会据此生成module的类名# build_mode_tag: ["EXAMPLE"] # 可选:指定模块的构建标签# pkg 会生成 ./helloworld/src/pkg/helloworld_pkg/pkg_main.cc
pkgs:- name: HelloWorld_pkg # 包名modules:- name: HelloWorld_module # 包含的模块# 部署 会生成 ./helloworld/src/install 目录及相应的启动脚本、配置文件
deploy_modes:- name: local_deploy # 部署模式名称deploy_ins: # 部署实例- name: local_ins_helloworld # 实例名称pkgs:- name: HelloWorld_pkg # 实例加载的包
运行aimrt_cli
工具,生成脚手架代码
aimrt_cli gen -p configuration_helloworld.yaml -o helloworld
module目录
HelloWorld_module
完整代码查看官方仓库,这里只讨论核心逻辑,建议对照源码阅读。
模块定义(helloworld_module.h
)
class HelloworldModule : public aimrt::ModuleBase {
public:// 必须实现的三大生命周期方法bool Initialize(aimrt::CoreRef core) override; // 初始化配置bool Start() override; // 业务逻辑入口void Shutdown() override; // 资源释放private:aimrt::CoreRef core_; // 框架核心句柄
};
模块实现(helloworld_module.cc
)
初始化阶段
AimRT会在这一过程初始化自己的资源、例如日志、通信等模块。
官方文档提到:需要注意的是,AimRT 的Initialize阶段仅仅是 AimRT 框架自身的初始化阶段,可能只是整个服务初始化阶段的一部分。使用者可能还需要在 AimRT 的Start阶段先初始化自己的一些业务逻辑,比如检查上下游资源、进行一些配置等,然后再真正的进入整个服务的运行阶段。
思考能否将自己的一些资源获取相关的业务逻辑也放在这一阶段(似乎没必要)
bool HelloworldModule::Initialize(aimrt::CoreRef core) {core_ = core; // 绑定框架核心// 读取 YAML 配置示例auto cfg_path = core_.GetConfigurator().GetConfigFilePath();YAML::Node cfg = YAML::LoadFile(cfg_path);AIMRT_INFO("Loaded config: {}", cfg["param"].as<std::string>());return true; // 初始化成功标志
}
运行阶段
初始化结束后,AimRT就会调用Start
进入运行阶段,执行开发人员自己的业务逻辑。
bool Start() {AIMRT_INFO("Module Started"); // 日志输出演示return true;
}
停止阶段
AimRT 收到停止信号后会调用Shutdown
方法,进行资源释放等操作。
void Shutdown() {AIMRT_INFO("Cleanup resources");
}
所有的业务逻辑我们都会在Module类中进行实现,具体加载运行方式会由AimRT根据配置文件进行集成
Pkg目录
Pkg模式集成(pkg_main.cc)
AimRT 提供的 aimrt_main
可执行程序,在运行时会根据配置文件加载 动态库 形式的 Pkg,并导入其中的 Module 类。加载后,框架会自动调用 Initialize
进行初始化,随后执行 Start
启动模块。
具体流程可参考 aimrt_main
源码:aimrt_main主函数代码
注册表定义
static std::tuple<std::string_view, FactoryFunc>aimrt_module_register_array[] = {{"HelloWorldModule", // 模块标识名[]() { return new HelloWorldModule(); }} // 工厂函数
};
- 模块名称:用于标识
HelloWorldModule
,框架会通过它查找模块。 - 工厂函数:返回
HelloWorldModule
的实例,供 AimRT 运行时调用。
注册宏
AIMRT_PKG_MAIN(aimrt_module_register_array) // 框架入口宏
AIMRT_PKG_MAIN
宏展开后,会生成 4 个 C 接口函数,供 aimrt_main
调用:
-
AimRTDynlibGetModuleNum()
:获取当前动态库中的模块数量。 -
AimRTDynlibGetModuleNameList()
:获取所有可用的模块名称。 -
AimRTDynlibCreateModule()
:根据模块名称创建模块实例。 -
AimRTDynlibDestroyModule()
:销毁模块实例,释放资源。
加载运行
运行 build
目录下的 start_local_deploy_local_ins_helloworld.sh
启动进程。
脚本名称 由创建工程时的((20250401152311-auw052s “配置文件”))自动生成。
# 这是启动脚本的具体内容
./aimrt_main --cfg_file_path=./cfg/local_deploy_local_ins_helloworld_cfg.yaml
运行流程:
- 解析配置文件,查找需要加载的模块名称。
- 在注册表中匹配模块名,调用对应的工厂函数创建实例。
- 依次执行
Initialize()
和 Start()
,完成模块启动。
App目录
**App模式官网概念:**开发者在自己的 Main 函数中注册/创建各个模块,编译时直接将业务逻辑编译进主程序;
可以简单理解为我们手动实现了aimrt_main
启动逻辑,分别有两种实现方式:创建模块 和 注册模块
核心接口
需要在CMake中引用相关库:
# Set link libraries of target
target_link_libraries(${CUR_TARGET_NAME}PRIVATE gflags::gflagsaimrt::runtime::core)
此时即可使用 core/aimrt_core.h
文件中的aimrt::runtime::core::AimRTCore
类,核心接口如下:
namespace aimrt::runtime::core {class AimRTCore {public:struct Options {std::string cfg_file_path;};public:void Initialize(const Options& options);void Start();std::future<void> AsyncStart();void Shutdown();module::ModuleManager& GetModuleManager();};} // namespace aimrt::runtime::core
接口使用说明如下:
-
void Initialize(const Options& options)
:用于初始化框架。- 接收一个
AimRTCore::Options
作为初始化参数。其中最重要的项是cfg_file_path
,用于设置配置文件路径。 - 如果初始化失败,会抛出一个异常。
- 接收一个
-
void Start()
:启动框架。- 如果启动失败,会抛出一个异常。
- 必须在 Initialize 方法之后调用,否则行为未定义。
- 如果启动成功,会阻塞当前线程,并将当前线程作为本
AimRTCore
实例的主线程。
-
std::future<void> AsyncStart()
:异步启动框架。- 如果启动失败,会抛出一个异常。
- 必须在 Initialize 方法之后调用,否则行为未定义。
- 如果启动成功,会返回一个
std::future<void>
句柄,需要在调用Shutdown
方法后调用该句柄的wait
方法阻塞等待结束。 - 该方法会在内部新启动一个线程作为本
AimRTCore
实例的主线程 - 阅读函数源码可以发现,创建的后台线程的唯一作用就是等待 shutdown 信号,并执行清理工作。
-
void Shutdown()
:停止框架。- 可以在任意线程、任意阶段调用此方法,也可以调用任意次数。
- 调用此方法后,
Start
方法将在执行完主线程中的所有任务后,退出阻塞。 - 需要注意,有时候业务会阻塞住主线程中的任务,导致
Start
方法无法退出阻塞、优雅结束整个框架,此时需要在外部强制 kill。
注册模块(helloworld_app_registration_mode
)(推荐方案)
通过RegisterModule
可以直接注册一个标准模块。开发者需要先实现一个继承于ModuleBase
基类的Module
,然后在AimRTCor
e实例调用Initialize
方法之前注册该Module
实例,在此方式下仍然有一个比较清晰的Module
边界。
参考代码:
#include <csignal>
#include <iostream>#include "HelloWorld_module/HelloWorld_module.h" // 这里引用的是我们之前创建的Module类
#include "core/aimrt_core.h"using namespace aimrt::runtime::core;
using namespace helloworld::HelloWorld_module;AimRTCore* global_core_ptr = nullptr;void SignalHandler(int sig) {if (global_core_ptr && (sig == SIGINT || sig == SIGTERM)) {global_core_ptr->Shutdown();return;}raise(sig);
};int32_t main(int32_t argc, char** argv) {signal(SIGINT, SignalHandler);signal(SIGTERM, SignalHandler);std::cout << "AimRT start." << std::endl;try {AimRTCore core;global_core_ptr = &core;// register moduleHelloworldModule helloworld_module;core.GetModuleManager().RegisterModule(helloworld_module.NativeHandle());AimRTCore::Options options;if (argc > 1) options.cfg_file_path = argv[1];core.Initialize(options);core.Start();core.Shutdown();global_core_ptr = nullptr;} catch (const std::exception& e) {std::cout << "AimRT run with exception and exit. " << e.what() << std::endl;return -1;}std::cout << "AimRT exit." << std::endl;return 0;
}
通过下面的命令启动编译后的可执行文件
./helloworld_app_registration_mode ./cfg/examples_cpp_helloworld_app_mode_cfg.yaml
**创建模块(**helloworld_app
)
在AimRTCore
实例调用Initialize方法之后,通过CreateModule
可以创建一个模块,并返回一个aimrt::CoreRef
句柄,开发者可以直接基于此句柄调用一些框架的方法,比如 RPC 或者 Log 等。在此方式下没有一个比较清晰的Module
边界,不利于大型项目的组织,一般仅用于快速做一些小工具。
参考代码:
// Copyright (c) 2023, AgiBot Inc.
// All rights reserved.#include <csignal>
#include <iostream>#include "aimrt_module_cpp_interface/core.h"
#include "core/aimrt_core.h"using namespace aimrt::runtime::core;// 全局指针,指向 AimRTCore 实例,用于在信号处理函数中进行关闭操作
AimRTCore* global_core_ptr = nullptr;/*** @brief 信号处理函数** 处理 SIGINT (Ctrl+C) 和 SIGTERM 信号,如果 AimRTCore 实例存在,则调用其* Shutdown 方法进行关闭。*/
void SignalHandler(int sig) {if (global_core_ptr && (sig == SIGINT || sig == SIGTERM)) {global_core_ptr->Shutdown(); // 调用 Shutdown 方法,优雅退出return;}raise(sig); // 传递其他信号,按照默认行为处理
}int32_t main(int32_t argc, char** argv) {// 注册信号处理函数,确保进程收到终止信号时能正确清理资源signal(SIGINT, SignalHandler);signal(SIGTERM, SignalHandler);std::cout << "AimRT 启动." << std::endl; // 日志:AimRT 启动try {AimRTCore core;global_core_ptr = &core; // 赋值全局指针,以便信号处理时可以访问// 配置 AimRTCore 选项AimRTCore::Options options;if (argc > 1)options.cfg_file_path =argv[1]; // 如果传入了参数,则使用该参数作为配置文件路径core.Initialize(options); // 初始化 AimRTCore 实例// 创建 AimRT 模块,并获取模块句柄aimrt::CoreRef module_handle(core.GetModuleManager().CreateModule("HelloWorldModule"));// 记录日志,表示模块创建成功AIMRT_HL_INFO(module_handle.GetLogger(), "这是一个示例日志。");// 读取并解析 YAML 配置文件auto file_path = module_handle.GetConfigurator().GetConfigFilePath();if (!file_path.empty()) {YAML::Node cfg_node = YAML::LoadFile(std::string(file_path));for (const auto& itr : cfg_node) {std::string k = itr.first.as<std::string>();std::string v = itr.second.as<std::string>();AIMRT_HL_INFO(module_handle.GetLogger(), "cfg [{} : {}]", k, v);}}// 异步启动 AimRT 实例,返回一个 future 对象auto fu = core.AsyncStart();AIMRT_HL_INFO(module_handle.GetLogger(), "启动成功。");// 日志:启动成功。// 等待 AimRT 运行结束(即 shutdown 触发)fu.wait();// 关闭 AimRT 实例core.Shutdown();// 清空全局指针,避免悬空指针问题global_core_ptr = nullptr;} catch (const std::exception& e) {std::cout << "AimRT 运行时发生异常,退出: " << e.what() << std::endl;return -1;}std::cout << "AimRT 退出." << std::endl; // 日志:AimRT 退出return 0;
}
通过下面的命令启动编译后的可执行文件:
./helloworld_app_registration_mode ./cfg/examples_cpp_helloworld_app_mode_cfg.yaml