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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/78752.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Tauri 跨平台开发指南及实战:用前端技术征服桌面应用(合集-万字长文)

厌倦了笨重的Electron应用&#xff1f;想要构建体积小、性能高、安全可靠的跨平台桌面应用&#xff1f;Tauri将是你的不二之选&#xff01;本教程带你从入门到精通&#xff0c;掌握这个下一代桌面应用开发框架&#xff0c;并通过实战APK分析工具项目&#xff0c;将理论知识转化…

【LeetCode 热题 100】矩阵置零 / 螺旋矩阵 / 旋转图像 / 搜索二维矩阵 II

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;LeetCode 热题 100 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 矩阵矩阵置零螺旋矩阵旋转图像搜索二维矩阵 II 矩阵 矩阵置零 矩阵置零 用两个数组分别标记行和列&#xff0c;判断…

JavaScript进阶(三十一): === 与 == 比较运算符

文章目录 一、前言二、严格相等运算符 ()三、宽松相等运算符 ()四、推荐做法五、特殊情况 一、前言 在 JavaScript 中&#xff0c; 和 都是比较运算符&#xff0c;但它们在比较时有重要区别&#xff1a; 二、严格相等运算符 () 不进行类型转换只有当两个操作数的值和类型都…

HTML与安全性:XSS、防御与最佳实践

HTML 与安全性&#xff1a;XSS、防御与最佳实践 前言 现代 Web 应用程序无处不在&#xff0c;而 HTML 作为其基础结构&#xff0c;承载着巨大的安全责任。跨站脚本攻击&#xff08;XSS&#xff09;仍然是 OWASP Top 10 安全威胁之一&#xff0c;对用户数据和网站完整性构成严…

安达发|破解医疗器械多BOM困局:APS生产计划排产软件解决方案

在医疗器械设备制造行业&#xff0c;生产计划与排程&#xff08;Advanced Planning and Scheduling, APS&#xff09;系统的应用至关重要。由于医疗器械行业具有严格的法规要求&#xff08;如FDA、ISO 13485&#xff09;、复杂的多级BOM&#xff08;Bill of Materials&#xff…

组件轮播与样式结构重用实验

任务一&#xff1a;使用“Swiper 轮播组件”对自行选择的图片和文本素材分别进行轮播&#xff0c;且调整对应的“loop”、“autoPlay”“interval”、“vertical”属性&#xff0c;实现不同的轮播效果&#xff0c;使用Swiper 样式自定义&#xff0c;修改默认小圆点和被选中小圆…

【Stable Diffusion】文生图进阶指南:采样器、噪声调度与迭代步数的解析

在Stable Diffusion文生图(Text-to-Image)的创作过程中,采样器(Sampler)、噪声调度器(Schedule type)和采样迭代步数(Steps)是影响生成效果的核心参数。本文将从技术原理、参数优化到实践应用,深入剖析DPM++ 2M采样器、Automatic噪声调度器以及采样步数的设计逻辑与协…

第一天 车联网定义、发展历程与生态体系

前言 车联网&#xff08;Internet of Vehicles, IoV&#xff09;作为物联网&#xff08;IoT&#xff09;在汽车领域的延伸&#xff0c;正在彻底改变人们的出行方式。无论是自动驾驶、远程诊断&#xff0c;还是实时交通优化&#xff0c;车联网技术都扮演着核心角色。本文将从零…

foc控制 - clarke变换和park变换

1. foc控制框图 下图是foc控制框图&#xff0c;本文主要是讲解foc控制中的larke变换和park变换clarke变换将 静止的 a b c abc abc坐标系 变换到 静止的 α β αβ αβ坐标系&#xff0c;本质上还是以 定子 为基准的坐标系park变换 则将 α β αβ αβ坐标系 变换到 随 转…

软件系统容量管理:反模式剖析与模式应用

在数字化时代&#xff0c;软件系统的重要性日益凸显。随着业务的不断拓展和用户数量的持续增长&#xff0c;软件系统的容量管理成为保障其高效运行的关键因素。《发布&#xff01;软件的设计与部署》第二部分围绕容量展开深入探讨&#xff0c;系统地阐述了容量的定义、范围&…

23种设计模式-行为型模式之解释器模式(Java版本)

Java 解释器模式&#xff08;Interpreter Pattern&#xff09;详解 &#x1f9e0; 什么是解释器模式&#xff1f; 解释器模式是一种行为型设计模式&#xff0c;主要用于解释和执行语言的语法规则。它定义了一个解释器来处理特定的语言句法&#xff0c;并通过一个抽象语法树来…

基于Springboot + vue + 爬虫实现的高考志愿智能推荐系统

项目描述 本系统包含管理员和学生两个角色。 管理员角色&#xff1a; 个人中心管理&#xff1a;管理员可以管理自己的个人信息。 高校信息管理&#xff1a;管理员可以查询、添加或删除高校信息&#xff0c;并查看高校详细信息。 学生管理&#xff1a;管理员可以查询、添加或…

五种机器学习方法深度比较与案例实现(以手写数字识别为例)

正如人们有各种各样的学习方法一样&#xff0c;机器学习也有多种学习方法。若按学习时所用的方法进行分类&#xff0c;则机器学习可分为机械式学习、指导式学习、示例学习、类比学习、解释学习等。这是温斯顿在1977年提出的一种分类方法。 有关机器学习的基本概念&#xff0c;…

Blender插件 三维人物角色动作自动绑定 Auto-Rig Pro V3.68.44 + Quick Rig V1.26.16

Auto-Rig Pro是一个集角色绑定、动画重定向和Unity、Unreal Engine的Fbx导出于一体的全能解决方案。最初作为我个人的内部角色绑定工具开发&#xff0c;我几年前将其发布&#xff0c;并自那时起增加了许多新功能。 Blender插件介绍 Auto-Rig Pro插件简介 Auto-Rig Pro是一个强…

网络基础概念:从菜鸟到入门

前言&#xff1a;快递小哥的故事 想象一下你要给朋友寄个礼物&#xff0c;这个过程其实和网络通信非常相似&#xff1a; 1. 你需要知道朋友的”地址“&#xff08;IP地址&#xff09; 2. 要注明是送到他家大门还是物业代收&#xff08;端口号&#xff09; 3. 要选择快递公司并…

23种设计模式-行为型模式之中介者模式(Java版本)

Java 中介者模式&#xff08;Mediator Pattern&#xff09;详解 &#x1f9e0; 什么是中介者模式&#xff1f; 中介者模式是一种行为型设计模式&#xff0c;它通过定义一个中介者对象来封装一组对象之间的交互。中介者使得各个对象不需要显式地知道彼此之间的关系&#xff0c…

【Redis】基础4:作为分布式锁

文章目录 1. 一些概念2. MySQL方案2.1 方案一&#xff1a;事务特性2.1.1 存在的问题2.1.2 解决方案 2.2 方案二&#xff1a;乐观锁2.3 方案三&#xff1a;悲观锁 3. Redis3.1 实现原理3.2 实现细节3.2.1 问题1&#xff1a;持有期间锁过期问题3.2.2 问题2&#xff1a;判断和释放…

深度学习---框架流程

核心六步 一、数据准备 二、模型构建 三、模型训练 四、模型验证 五、模型优化 六、模型推理 一、数据准备&#xff1a;深度学习的基石 数据是模型的“燃料”&#xff0c;其质量直接决定模型上限。核心步骤包括&#xff1a; 1. 数据收集与标注 来源&#xff1a;公开数据集…

阿里云 OpenManus 实战:高效AI协作体系

阿里云 OpenManus 实战&#xff1a;高效AI协作体系 写在最前面初体验&#xff1a;快速部署&#xff0c;开箱即用 真实案例分享&#xff1a;从单体开发到智能良好提示词过程展示第一步&#xff1a;为亚马逊美国站生成商品描述第二步&#xff1a;为eBay全球站生成商品描述结果分析…

Kubernetes》》k8s》》explain查 yaml 参数

在创建json 和yaml 时&#xff0c;我们可能不知道具体的参数该怎么写。同样 我们可以通过explain这个 命令来查看 每个参数具体的作用与写法 # 查看 pod类性有哪些参数 kubectl explain pod# 查看pod中 spec下面有哪些参数 kubectl explain pod.spec