当前位置: 首页 > news >正文

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

运行流程:

  1. 解析配置文件,查找需要加载的模块名称。
  2. 在注册表中匹配模块名,调用对应的工厂函数创建实例。
  3. 依次执行 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

相关文章:

  • 【C++11】新的类功能、lambda
  • 存储器分类
  • 【网络】HTTP报文首部字段
  • Qt的WindowFlags窗口怎么选?
  • LeetCode 2906 统计最大元素出现至少K次的子数组(滑动窗口)
  • oracle怎样通过固化较优执行计划来优化慢sql
  • ant design pro 项目发布遇到登录页访问404
  • 【免费下载】2012-2023年全国夜间灯光数据
  • 从遍历序列构造二叉树:前序+中序与中序+后序的递归解法详解
  • JavaScript面试问题
  • tbb parallel_for 使用
  • 颜色分类,不靠“调色盘”:双指针 VS 计数排序的正面PK
  • 【Linux】服务自启动设置的方式
  • LayerSkip: Enabling Early Exit Inference and Self-Speculative Decoding
  • Java之BigDecimal
  • Silvaco仿真中victory process的蒙特卡洛(Monte Carlo)离子注入
  • 深入理解 Linux 权限管理:从基础到进阶
  • 【GESP】C++三级练习 luogu-B2117 整理药名
  • (三十二)Android开发中AppCompatActivity和Activity之间的详细区别
  • 2025运维工程师面试题1(答案在后一张)
  • 4月人民币对美元即期汇率微跌,今年以来升值0.48%
  • 广东省副省长刘红兵跨省调任湖南省委常委、宣传部长
  • 豆神教育:2024年净利润1.37亿元,同比增长334%
  • 美国通过《删除法案》:打击未经同意发布他人私密图像,包括“深度伪造”
  • 比熬夜更伤肝的事,你可能每天都在做
  • 绿地控股:今年一季度营业收入356亿元,中高层管理人员带头降薪