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