AimRT 从零到一:官方示例精讲 —— 六、pb_chn示例.md
pb_chn示例
官方仓库:pb_chn
这个官方示例展示了如何基于 protobuf 协议和 local 后端实现 channel 通信。主要包括四种场景:
- 基础示例:分别用两个模块(Publisher 和 Subscriber)发布和订阅 protobuf 消息,使用 local channel 通信。
- 单 Pkg 集成:与基础示例类似,但将发布和订阅模块集成到同一个 Pkg 运行。
- Publisher App 模式:以 App 模式直接运行 Publisher,同时以 Module 方式运行 Subscriber。
- Subscriber App 模式:以 App 模式直接运行 Subscriber,同时以 Module 方式运行 Publisher。
需要先阅读一下官方文档的相关内容:Channel 章节
这里我们没有复现benchmark
两个模块,只关注通信相关内容。
配置文件(configuration_protobuf_channel.yaml)
依据官方示例项目结构自行编写YAML配置文件:
# ============== 基础信息 ==============
base_info:project_name: Protobuf_channel # 项目名称build_mode_tags: ["EXAMPLE", "SIMULATION", "TEST_CAMERA"] # 构建模式标签aimrt_import_options: # AimRT 框架构建选项AIMRT_BUILD_WITH_PROTOBUF: "ON" # 启用 Protobuf 支持(必须开启)AIMRT_BUILD_RUNTIME: "ON" # 启用模块化运行支持(必须开启)AIMRT_BUILD_TESTS: "OFF" # 关闭测试模块构建AIMRT_BUILD_EXAMPLES: "OFF" # 关闭示例构建(实际跑示例需打开)AIMRT_BUILD_DOCUMENT: "OFF" # 关闭文档构建AIMRT_BUILD_CLI_TOOLS: "OFF" # 关闭命令行工具AIMRT_USE_LOCAL_PROTOC_COMPILER: "OFF" # 使用内置 Protoc 编译器AIMRT_BUILD_WITH_ROS2: "OFF" # 关闭 ROS2 支持AIMRT_BUILD_NET_PLUGIN: "OFF" # 关闭网络插件AIMRT_BUILD_ROS2_PLUGIN: "OFF" # 关闭 ROS2 插件# ============== 协议配置 ==============
protocols:- name: my_prototype: protobufoptions:xxx: xxx # 预留可扩展选项# 以下为注释掉的备用协议示例:# - name: my_ros2_proto# type: ros2# options:# zzz: zzz# build_mode_tag: ["EXAMPLE"]## - name: example_proto# type: protobuf# build_mode_tag: ["EXAMPLE"] # 仅在 EXAMPLE 模式启用# ============== 模块定义 ==============
modules:- name: normal_publisher_module # 定期发布 ExampleEventMsg 消息- name: normal_subscriber_module # 订阅并处理 ExampleEventMsg 消息# ============== 包组合配置 ==============
pkgs:- name: protobuf_channel_pkg # 单包整合示例modules:- name: normal_publisher_module- name: normal_subscriber_module- name: protobuf_channel_pub_pkg # 发布模块包(仅发布者)modules:- name: normal_publisher_module- name: protobuf_channel_sub_pkg # 订阅模块包(仅订阅者)modules:- name: normal_subscriber_module# ============== 部署方案 ==============
deploy_modes:- name: local_deploy # 本地部署方案deploy_ins:- name: local_ins_protobuf_channel # 双包部署(发布+订阅分开)pkgs:- name: protobuf_channel_pub_pkg- name: protobuf_channel_sub_pkg- name: local_ins_protobuf_channel_single_pkg # 单包部署(发布+订阅合一)pkgs:- name: protobuf_channel_pkg## ========================================================
## 官方工具似乎不支持 App 模式,因此相关文件需要手动配置。
## 发布配置主要影响后续生成的 cfg 文件和启动脚本。
## 可以将以下内容添加到配置文件中,但仍需手动调整。
## ========================================================
# - name: local_ins_protobuf_channel_publisher_app # app发布
# pkgs:
# - name: protobuf_channel_sub_pkg # 订阅模块包,用于订阅app发布的消息# - name: local_ins_protobuf_channel_subscriber_app # app订阅
# pkgs:
# - name: protobuf_channel_pub_pkg # 发布模块包,用于向app发布消息
protocols目录
这是配置文件中 协议 部分生成的目录,管理通信协议相关的文件
protocols/
└── my_proto├── CMakeLists.txt└── my_proto.proto
my_proto.proto文件
用来定义通信的消息类型
syntax = "proto3";package Protobuf_channel.my_proto; // 命名空间:Protobuf_channel::my_proto// 在这里定义您的消息
message ExampleEventMsg {string msg = 1;int32 num = 2;
}
CMakeLists.txt文件
AimRT框架封装了从Protobuf生成c++代码的操作,只需要使用提供的CMake 方法即可。相关内容见官方文档:Protobuf封装
# Get the current folder name
string(REGEX REPLACE ".*/\(.*\)" "\\1" CUR_DIR ${CMAKE_CURRENT_SOURCE_DIR})# Get namespace
get_namespace(CUR_SUPERIOR_NAMESPACE)
string(REPLACE "::" "_" CUR_SUPERIOR_NAMESPACE_UNDERLINE ${CUR_SUPERIOR_NAMESPACE})# Set target name
set(CUR_TARGET_NAME ${CUR_SUPERIOR_NAMESPACE_UNDERLINE}_${CUR_DIR})
set(CUR_TARGET_ALIAS_NAME ${CUR_SUPERIOR_NAMESPACE}::${CUR_DIR})# Add target
add_protobuf_gencode_target_for_proto_path(TARGET_NAME ${CUR_TARGET_NAME}_pb_gencodePROTO_PATH ${CMAKE_CURRENT_SOURCE_DIR}GENCODE_PATH ${CMAKE_CURRENT_BINARY_DIR})
add_library(${CUR_TARGET_ALIAS_NAME}_pb_gencode ALIAS ${CUR_TARGET_NAME}_pb_gencode)# Set installation
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}DESTINATION "share"FILES_MATCHINGPATTERN "*.proto")set_property(TARGET ${CUR_TARGET_NAME}_pb_gencode PROPERTY EXPORT_NAME ${CUR_TARGET_ALIAS_NAME}_pb_gencode)
install(TARGETS ${CUR_TARGET_NAME}_pb_gencodeEXPORT ${INSTALL_CONFIG_NAME}ARCHIVE DESTINATION libFILE_SET HEADERSDESTINATION include/${CUR_TARGET_NAME}_pb_gencode)
最终生成的Target需要链接到模块,可以在Cmake文件最后添加下面的内容,查看Target名称
# 打印最终Target名称
message(STATUS "最终Target名称 - CUR_TARGET_NAME: ${CUR_TARGET_NAME}_pb_gencode")
module目录
normal_publisher_module
链接生成的消息文件(CMakeLists.txt
)
# 略# Set link libraries of target
target_link_libraries(${CUR_TARGET_NAME}PRIVATE yaml-cpp::yaml-cppPUBLIC aimrt::interface::aimrt_module_cpp_interfacePUBLIC Protobuf_channel_my_proto_pb_gencode) # 在这里进行链接
模块定义(normal_publisher_module.h
)
#pragma once
#include <atomic>
#include <future>#include "aimrt_module_cpp_interface/module_base.h"namespace Protobuf_channel::normal_publisher_module {
using namespace aimrt;/*** @brief 普通发布者模块类** 该模块用于定期发布Protobuf格式的消息到指定主题*/
class NormalPublisherModule : public aimrt::ModuleBase {public:NormalPublisherModule() = default;~NormalPublisherModule() override = default;// 获取模块基本信息ModuleInfo Info() const override {return ModuleInfo{.name = "NormalPublisherModule"};}bool Initialize(aimrt::CoreRef core) override;bool Start() override;void Shutdown() override;private:// 获取日志记录器auto GetLogger() { return core_.GetLogger(); }// 主工作循环void MainLoop();private:aimrt::CoreRef core_; // 核心系统引用aimrt::executor::ExecutorRef executor_; // 任务执行器引用std::atomic_bool run_flag_ = false; // 运行状态标志std::promise<void> stop_sig_; // 停止信号量std::string topic_name_ = "test_topic"; // 发布主题名称double channel_frq_ = 0.5; // 发布频率(Hz)aimrt::channel::PublisherRef publisher_; // 消息发布器引用
};} // namespace Protobuf_channel::normal_publisher_module
- 定期发布Protobuf格式的消息到指定主题
模块实现(normal_publisher_module.cc
)
初始化阶段
bool NormalPublisherModule::Initialize(aimrt::CoreRef core) {core_ = core; // 保存 CoreRef,后续模块操作需要用到try {// === Step 1: 从配置文件读取 topic 和发布频率 ===auto file_path = core_.GetConfigurator().GetConfigFilePath();if (!file_path.empty()) {YAML::Node cfg_node = YAML::LoadFile(std::string(file_path));topic_name_ = cfg_node["topic_name"].as<std::string>();channel_frq_ = cfg_node["channel_frq"].as<double>();}// === Step 2: 获取执行器 ExecutorRef(用来安排任务执行) ===executor_ = core_.GetExecutorManager().GetExecutor("work_thread_pool");AIMRT_CHECK_ERROR_THROW(executor_ && executor_.SupportTimerSchedule(),"获取执行器'work_thread_pool'失败");// ➔ Note: Executor 是 AimRT// 中安排异步任务的组件,类似“线程池”或“定时器调度器”// === Step 3: 获取 PublisherRef(即通道的发布句柄) ===publisher_ = core_.GetChannelHandle().GetPublisher(topic_name_);AIMRT_CHECK_ERROR_THROW(publisher_, "获取主题'{}'的发布者失败",topic_name_);// ➔ Note: PublisherRef 是发布消息用的关键对象,相当于“话题写入器”// === Step 4: 注册消息类型(必须要做!)===bool ret = aimrt::channel::RegisterPublishType<Protobuf_channel::my_proto::ExampleEventMsg>(publisher_);AIMRT_CHECK_ERROR_THROW(ret, "注册发布类型失败");// ➔ Note: 必须告诉系统:这个 Publisher 上发送的是 ExampleEventMsg 类型的// Protobuf 消息。} catch (const std::exception& e) {AIMRT_ERROR("初始化失败, 错误信息: {}", e.what());return false;}AIMRT_INFO("模块初始化成功");return true;
}
-
获取了配置文件中的相关配置
-
获取了执行器
executor_
-
获取了消息发布者
publisher_
-
对消息类型进行注册
运行阶段
bool NormalPublisherModule::Start() {try {run_flag_ = true; // 设置运行标志,控制主循环// === 启动主循环任务 ===executor_.Execute(std::bind(&NormalPublisherModule::MainLoop, this));// ➔ Note: 这里没有直接启动线程,而是通过 executor 异步调度执行// MainLoop(即委托给了 AimRT 的调度器管理)} catch (const std::exception& e) {AIMRT_ERROR("模块启动失败, 错误信息: {}", e.what());return false;}AIMRT_INFO("模块启动成功");return true;
}void NormalPublisherModule::MainLoop() {try {AIMRT_INFO("主工作循环开始运行");// === 创建 PublisherProxy,简化消息发布过程 ===aimrt::channel::PublisherProxy<Protobuf_channel::my_proto::ExampleEventMsg>publisher_proxy(publisher_);// ➔ Note: 绑定 publisher_,后面发布时只需要传 msg,不用每次带 PublisherRef// ➔ Note: Proxy 还能管理 Context,更高级uint32_t count = 0;while (run_flag_) {// === 按配置的发布频率休眠 ===std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<uint32_t>(1000 / channel_frq_)));count++;AIMRT_INFO("发布循环计数: {}", count);// === 构造并填充消息 ===Protobuf_channel::my_proto::ExampleEventMsg msg;msg.set_msg("当前计数: " + std::to_string(count)); // 设置文本字段msg.set_num(count); // 设置数字字段// === 发布消息 ===AIMRT_INFO("发布新消息, 内容: {}", aimrt::Pb2CompactJson(msg));publisher_proxy.Publish(msg);// ➔ Note: Publish是异步的,实际发送取决于通道后端的实现// ➔ Note: 如果后端阻塞,可能导致 Publish 耗时不稳定,但一般很快返回}AIMRT_INFO("主工作循环正常退出");} catch (const std::exception& e) {AIMRT_ERROR("主工作循环异常退出, 错误信息: {}", e.what());}// 通知 Shutdown 主线程,主循环已经结束stop_sig_.set_value();
}
-
通过执行器绑定
MainLoop
函数 -
MainLoop
函数首先创建了一个发布代理publisher_proxy
,用来简化消息发布操作(绑定 publisher_,后面发布时只需要传 msg,不用每次带 PublisherRef) -
构造并填充消息,进行发布
停止阶段
void NormalPublisherModule::Shutdown() {try {if (run_flag_) {run_flag_ = false; // 通知主循环退出stop_sig_.get_future().wait(); // 等待 MainLoop 退出信号}} catch (const std::exception& e) {AIMRT_ERROR("模块关闭失败, 错误信息: {}", e.what());return;}AIMRT_INFO("模块已正常关闭");
}
- 通过信号量通知进行优雅退出
normal_subscriber_module
链接生成的消息文件(CMakeLists.txt
)
# 略# Set link libraries of target
target_link_libraries(${CUR_TARGET_NAME}PRIVATE yaml-cpp::yaml-cppPUBLIC aimrt::interface::aimrt_module_cpp_interfacePUBLIC Protobuf_channel_my_proto_pb_gencode) # 链接生成的消息文件
模块定义(normal_subscriber_module.h
)
#pragma once#include "aimrt_module_cpp_interface/module_base.h"
#include "my_proto.pb.h" // 自定义Protobuf消息头文件namespace Protobuf_channel::normal_subscriber_module {
using namespace aimrt;/*** @brief 普通订阅者模块类** 该模块用于订阅并处理Protobuf格式的消息*/
class NormalSubscriberModule : public aimrt::ModuleBase {public:NormalSubscriberModule() = default;~NormalSubscriberModule() override = default;// 获取模块基本信息ModuleInfo Info() const override {return ModuleInfo{.name = "NormalSubscriberModule"};}bool Initialize(aimrt::CoreRef core) override; // 初始化模块bool Start() override; // 启动模块void Shutdown() override; // 关闭模块private:// 获取日志记录器auto GetLogger() { return core_.GetLogger(); }/*** @brief 事件处理回调函数* @param ctx 消息上下文* @param data 接收到的消息数据*/void EventHandle(aimrt::channel::ContextRefctx, // 上下文信息是由AimRT系统框架传递的,在编译期确定const std::shared_ptr<const Protobuf_channel::my_proto::ExampleEventMsg>&data);private:aimrt::CoreRef core_; // 核心系统引用std::string topic_name_ = "test_topic"; // 订阅主题名称aimrt::channel::SubscriberRef subscriber_; // 消息订阅者引用
};} // namespace Protobuf_channel::normal_subscriber_module
模块实现(normal_subscriber_module.cc
)
初始化阶段
bool NormalSubscriberModule::Initialize(aimrt::CoreRef core) {core_ = core; // 保存 CoreRef,后续操作模块需要用到try {// === Step 1: 读取配置文件,拿到订阅的话题名 ===auto file_path = core_.GetConfigurator().GetConfigFilePath();if (!file_path.empty()) {YAML::Node cfg_node = YAML::LoadFile(std::string(file_path));topic_name_ = cfg_node["topic_name"].as<std::string>();}// === Step 2: 获取SubscriberRef,准备订阅主题 ===subscriber_ = core_.GetChannelHandle().GetSubscriber(topic_name_);AIMRT_CHECK_ERROR_THROW(subscriber_, "获取主题'{}'的订阅者失败",topic_name_);// === Step 3: 注册消息订阅回调 ===bool ret =aimrt::channel::Subscribe<Protobuf_channel::my_proto::ExampleEventMsg>(subscriber_,std::bind(&NormalSubscriberModule::EventHandle, this,std::placeholders::_1, std::placeholders::_2));AIMRT_CHECK_ERROR_THROW(ret, "订阅消息失败");// ➔ Note: 这里使用的是【函数风格】【带 Context// 参数】【智能指针回调】的订阅形式// ➔ Note: 必须在 Initialize阶段完成订阅,运行中不允许动态订阅} catch (const std::exception& e) {AIMRT_ERROR("初始化失败,错误信息: {}", e.what());return false;}AIMRT_INFO("模块初始化成功");return true;
}void NormalSubscriberModule::EventHandle(aimrt::channel::ContextRef ctx,const std::shared_ptr<const Protobuf_channel::my_proto::ExampleEventMsg>&data) {// === Step 4: 处理收到的新消息 ===AIMRT_INFO("收到新消息,上下文: {}, 数据内容: {}", ctx.ToString(),aimrt::Pb2CompactJson(*data));// ➔ Note: ContextRef// 提供了消息链路、来源、发送时间等上下文信息,可以根据需要提取// ➔ Note: data 是 Protobuf 消息的【const智能指针】,直接解引用使用即可
}
- 获取配置文件中的相关配置
- 获取订阅者
subscriber_
- 注册消息订阅回调
EventHandle
函数 - 注意点:可以看到回调函数会接收一个
ctx
上下文参数,而我们编写的发布者并没有看到这个数据。 - 这个上下文参数是由AimRT框架自动传入的,也可以只保留
data
一个参数。这一过程是在编译期确定的
运行阶段
bool NormalSubscriberModule::Start() {// 当前 Start 阶段无特定动作,AimRT 框架会自动管理订阅回调的调度return true;
}
停止阶段
void NormalSubscriberModule::Shutdown() {// 当前 Shutdown 阶段无特定动作,SubscriberRef 生命周期随模块销毁而结束
}
启动Pkg模式通信示例
protobuf channel
一个最基本的、基于 protobuf 协议与 local 后端的 channel 示例,演示内容包括:
- 如何使用 protobuf 协议作为 channel 传输协议;
- 如何基于 Module 方式使用 Executor、Channel publish 和 subscribe 功能;
- 如何使用 local 类型的 channel 后端;
- 如何以 Pkg 模式集成 Module 并启动;
对应配置文件(cfg/local_deploy_local_ins_protobuf_channel_cfg.yaml
)
aimrt:log:core_lvl: INFO # Trace/Debug/Info/Warn/Error/Fatal/Offbackends:- type: consoleexecutor:executors:- name: work_thread_pooltype: asio_threadoptions:thread_num: 4# 消息通道相关配置官方文档:https://docs.aimrt.org/v0.10.0/tutorials/cfg/channel.html#aimrt-channelchannel:backends:- type: local # local类型的 Channel 后端是 AimRT 官方提供的一种 Channel 后端,用于将消息发布到同进程中的其他模块,它会自动判断发布端和订阅端是否在同一个Pkg内,从而采用各种方式进行性能的优化options:subscriber_use_inline_executor: false # 如果配置为true,则直接使用发布端的执行器来执行订阅端的回调,会阻塞发布端的 Publish 方法直到所有的订阅端回调都执行完成subscriber_executor: work_thread_pool # 仅在subscriber_use_inline_executor为false时生效,后端会将订阅端的回调都投递进此执行器中异步执行,发布端的 Publish 方法会立即返回pub_topics_options:- topic_name: "(.*)"enable_backends: [local]sub_topics_options:- topic_name: "(.*)"enable_backends: [local]module:pkgs:- path: ./libprotobuf_channel_pub_pkg.so- path: ./libprotobuf_channel_sub_pkg.somodules:- name: NormalPublisherModulelog_lvl: INFO- name: NormalSubscriberModulelog_lvl: INFO# 模块自定义配置
NormalPublisherModule:topic_name: test_topic # topic名称channel_frq: 0.5 # 0.5HzNormalSubscriberModule:topic_name: test_topic
消息通道相关配置官方文档:配置文件-通道
说明:
-
此示例创建了以下两个模块:
-
NormalPublisherModule
:会基于work_thread_pool
执行器,以配置的频率、向配置的 topic 中发布ExampleEventMsg
类型的消息; -
NormalSubscriberModule
:会订阅配置的 topic 下的ExampleEventMsg
类型的消息;
-
-
此示例将
NormalPublisherModule
和NormalSubscriberModule
分别集成到pb_chn_pub_pkg
和pb_chn_sub_pkg
两个 Pkg 中,并在配置文件中加载这两个 Pkg 到一个 AimRT 进程中; -
此示例使用 local 类型的 channel 后端进行通信;
以pkg模式启动
启动命令为:
./aimrt_main --cfg_file_path=./cfg/local_deploy_local_ins_protobuf_channel_cfg.yaml
如果是通过aimrt_cli
工具生成代码框架,则执行start_local_deploy_local_ins_protobuf_channel.sh
脚本
protobuf channel single pkg
一个最基本的、基于 protobuf 协议与 local 后端的 channel 示例,演示内容包括:
- 如何使用 protobuf 协议作为 channel 传输协议;
- 如何基于 Module 方式使用 Executor、Channel publish 和 subscribe 功能;
- 如何使用 local 类型的 channel 后端;
- 如何以 Pkg 模式集成 Module 并启动;
对应配置文件(cfg/local_deploy_local_ins_protobuf_channel_single_pkg_cfg.yaml
)
aimrt:log:core_lvl: INFO # Trace/Debug/Info/Warn/Error/Fatal/Offbackends:- type: consoleexecutor:executors:- name: work_thread_pooltype: asio_threadoptions:thread_num: 4channel:backends:- type: localoptions:subscriber_use_inline_executor: falsesubscriber_executor: work_thread_poolpub_topics_options:- topic_name: "(.*)"enable_backends: [local]sub_topics_options:- topic_name: "(.*)"enable_backends: [local]module:pkgs:- path: ./libprotobuf_channel_pkg.sodisable_modules: []modules:- name: NormalPublisherModulelog_lvl: INFO- name: NormalSubscriberModulelog_lvl: INFO# Module custom configuration
NormalPublisherModule:topic_name: test_topicchannel_frq: 0.5NormalSubscriberModule:topic_name: test_topic
说明:
- 此示例与 protobuf channel 示例基本一致,唯一的区别是将
NormalPublisherModule
和NormalSubscriberModule
集成到protobuf_channel_pkg
一个 Pkg 中;
以pkg模式启动
启动命令为:
./aimrt_main --cfg_file_path=./cfg/local_deploy_local_ins_protobuf_channel_single_pkg_cfg.yaml
如果是通过aimrt_cli
工具生成代码框架,则执行start_local_deploy_local_ins_protobuf_channel_single_pkg.sh
脚本
App目录
normal_pb_chn_publisher_app
链接Protobuf生成的消息文件(CMakeLists.txt
)
# 略target_link_libraries(${CUR_TARGET_NAME}PRIVATE gflags::gflagsaimrt::runtime::coreaimrt::interface::aimrt_module_protobuf_interfaceProtobuf_channel_my_proto_pb_gencode)
主函数(main.cpp
)
#include <csignal> // 信号处理
#include <iostream> // 标准输入输出// 包含AIMRT框架相关头文件
#include "aimrt_module_cpp_interface/core.h"
#include "aimrt_module_protobuf_interface/channel/protobuf_channel.h"
#include "aimrt_module_protobuf_interface/util/protobuf_tools.h"
#include "core/aimrt_core.h"
#include "my_proto.pb.h" // Protobuf生成的消息定义using namespace aimrt::runtime::core; // 使用AIMRT核心命名空间bool run_flag = true; // 全局运行标志// 信号处理函数
void SignalHandler(int sig) {if (sig == SIGINT || sig == SIGTERM) { // 处理中断和终止信号run_flag = false; // 设置运行标志为false以优雅退出return;}raise(sig); // 其他信号原样抛出
};// 主函数
int32_t main(int32_t argc, char** argv) {// 注册信号处理函数signal(SIGINT, SignalHandler);signal(SIGTERM, SignalHandler);std::cout << "AimRT 启动." << std::endl;try {AimRTCore core; // 创建AIMRT核心对象// 初始化配置AimRTCore::Options options;if (argc > 1)options.cfg_file_path = argv[1]; // 从命令行参数获取配置文件路径core.Initialize(options); // 初始化核心// 创建模块aimrt::CoreRef module_handle(core.GetModuleManager().CreateModule("NormalPublisherModule")); // 创建发布者模块std::string topic_name = "test_topic"; // 默认主题名double channel_frq = 0.5; // 默认发布频率(Hz)// 读取配置文件auto file_path = module_handle.GetConfigurator().GetConfigFilePath();if (!file_path.empty()) {YAML::Node cfg_node =YAML::LoadFile(std::string(file_path)); // 加载YAML配置topic_name = cfg_node["topic_name"].as<std::string>(); // 获取主题名配置channel_frq = cfg_node["channel_frq"].as<double>(); // 获取发布频率配置}// 注册发布类型auto publisher = module_handle.GetChannelHandle().GetPublisher(topic_name); // 获取发布者// 检查发布者是否有效(失败会抛出异常)AIMRT_HL_CHECK_ERROR_THROW(module_handle.GetLogger(), publisher,"获取主题'{}'的发布者失败", topic_name);// 创建发布者代理(用于发布Protobuf消息)aimrt::channel::PublisherProxy<Protobuf_channel::my_proto::ExampleEventMsg>publisher_proxy(publisher);bool ret = publisher_proxy.RegisterPublishType(); // 注册发布类型AIMRT_HL_CHECK_ERROR_THROW(module_handle.GetLogger(), ret,"注册发布类型失败");// 启动核心(异步)auto fu = core.AsyncStart();// 发布消息循环uint32_t count = 0;while (run_flag) {// 按配置频率休眠std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<uint32_t>(1000 / channel_frq)));count++;AIMRT_HL_INFO(module_handle.GetLogger(),"循环计数 : {} -------------------------", count);// 构造并发布Protobuf消息Protobuf_channel::my_proto::ExampleEventMsg msg;msg.set_msg("计数: " + std::to_string(count)); // 设置消息内容msg.set_num(count); // 设置消息编号// 记录日志并发布AIMRT_HL_INFO(module_handle.GetLogger(), "发布新的Protobuf事件, 数据: {}",aimrt::Pb2CompactJson(msg)); // 将Protobuf转为JSON格式日志publisher_proxy.Publish(msg); // 发布消息}// 优雅关闭core.Shutdown(); // 关闭核心fu.wait(); // 等待异步操作完成} catch (const std::exception& e) {std::cout << "AimRT 运行异常并退出: " << e.what() << std::endl;return -1; // 异常退出}std::cout << "AimRT 退出." << std::endl; // 正常退出日志return 0;
}
-
基于 App 模式创建模块的方式使用 Channel publish 功能
-
创建 NormalPublisherModule 模块,获取 CoreRef 句柄,以配置的频率、向配置的 topic 中发布 ExampleEventMsg 类型的消息
normal_pb_chn_subscriber_app
链接Protobuf生成的消息文件(CMakeLists.txt
)
target_link_libraries(${CUR_TARGET_NAME}PRIVATE gflags::gflagsaimrt::runtime::coreaimrt::interface::aimrt_module_protobuf_interfaceProtobuf_channel_my_proto_pb_gencode)
主函数(main.cpp
)
#include <csignal>
#include <iostream>#include "aimrt_module_cpp_interface/core.h"
#include "aimrt_module_protobuf_interface/channel/protobuf_channel.h"
#include "aimrt_module_protobuf_interface/util/protobuf_tools.h"
#include "core/aimrt_core.h"
#include "my_proto.pb.h"using namespace aimrt::runtime::core;// 全局核心指针,用于信号处理
AimRTCore* global_core_ptr = nullptr;/*** @brief 信号处理函数* @param sig 接收到的信号*/
void SignalHandler(int sig) {// 如果是SIGINT(中断)或SIGTERM(终止)信号,且全局核心指针有效,则优雅关闭if (global_core_ptr && (sig == SIGINT || sig == SIGTERM)) {global_core_ptr->Shutdown();return;}// 其他信号继续传递raise(sig);
};/*** @brief 主函数*/
int32_t main(int32_t argc, char** argv) {// 注册信号处理函数signal(SIGINT, SignalHandler);signal(SIGTERM, SignalHandler);std::cout << "AimRT 启动中..." << std::endl;try {// 创建核心对象AimRTCore core;global_core_ptr = &core;// 初始化配置AimRTCore::Options options;if (argc > 1) {options.cfg_file_path = argv[1]; // 从命令行参数获取配置文件路径}core.Initialize(options);// 创建模块aimrt::CoreRef module_handle(core.GetModuleManager().CreateModule("NormalSubscriberModule"));std::string topic_name = "test_topic"; // 默认主题名// 从配置文件读取主题名auto file_path = module_handle.GetConfigurator().GetConfigFilePath();if (!file_path.empty()) {YAML::Node cfg_node = YAML::LoadFile(std::string(file_path));topic_name = cfg_node["topic_name"].as<std::string>();}// 获取订阅者auto subscriber =module_handle.GetChannelHandle().GetSubscriber(topic_name);AIMRT_HL_CHECK_ERROR_THROW(module_handle.GetLogger(), subscriber,"获取主题 '{}' 的订阅者失败", topic_name);// 订阅事件bool ret =aimrt::channel::Subscribe<Protobuf_channel::my_proto::ExampleEventMsg>(subscriber,[module_handle](const std::shared_ptr<const Protobuf_channel::my_proto::ExampleEventMsg>& data) {// 收到消息时的回调函数AIMRT_HL_INFO(module_handle.GetLogger(),"收到新的PB协议事件消息, 内容: {}",aimrt::Pb2CompactJson(*data));});AIMRT_HL_CHECK_ERROR_THROW(module_handle.GetLogger(), ret, "订阅失败");// 启动核心core.Start();// 关闭核心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;return 0;
}
- 基于 App 模式创建模块的方式使用 Channel subscribe 功能
- 创建 NormalSubscriberModule 模块,获取 CoreRef 句柄,会订阅配置的 topic 下的 ExampleEventMsg 类型的消息
启动APP模式通信示例
protobuf channel publisher app
一个基于 protobuf 协议与 local 后端的 channel 示例,演示内容包括:
- 如何使用 protobuf 协议作为 channel 传输协议;
- 如何基于 App 模式创建模块的方式使用 Channel publish 功能;
- 如何基于 Module 方式使用 Channel subscribe 功能;
- 如何使用 local 类型的 channel 后端;
- 如何以 App 模式启动;
配置文件(cfg/local_deploy_local_ins_protobuf_channel_publisher_app_cfg.yaml
)
aimrt:log:core_lvl: INFO # Trace/Debug/Info/Warn/Error/Fatal/Offbackends:- type: consoleexecutor:executors:- name: work_thread_pooltype: asio_threadoptions:thread_num: 4channel:backends:- type: localoptions:subscriber_use_inline_executor: falsesubscriber_executor: work_thread_poolpub_topics_options:- topic_name: "(.*)"enable_backends: [local]sub_topics_options:- topic_name: "(.*)"enable_backends: [local]module:pkgs:- path: ./libprotobuf_channel_sub_pkg.so # 订阅方使用的是之前以module模式编写的包enable_modules: []modules:- name: NormalPublisherModule # 启动发布模块 (App模式编写)log_lvl: INFO- name: NormalSubscriberModule # 启动订阅模块 (pkg模式编写)log_lvl: INFO# Module custom configuration
NormalPublisherModule:topic_name: test_topicchannel_frq: 0.5NormalSubscriberModule:topic_name: test_topic
以app模式启动
启动命令为:
./normal_pb_chn_publisher_app ./cfg/local_deploy_local_ins_protobuf_channel_publisher_app_cfg.yaml
**注意:**aim_cli
工具不支持生成App模式代码框架,因此不会有相应的启动脚本,需要自行编写。
- 可以在生成代码框架时,先使用pkg模式进行设置,之后对生成的配置文件、启动脚本进行修改即可
编写一个app模式的启动脚本start_local_deploy_local_ins_protobuf_channel_publisher_app.sh
:
#!/bin/bash./normal_pb_chn_publisher_app ./cfg/local_deploy_local_ins_protobuf_channel_publisher_app_cfg.yaml
protobuf channel subscriber app
一个基于 protobuf 协议与 local 后端的 channel 示例,演示内容包括:
- 如何使用 protobuf 协议作为 channel 传输协议;
- 如何基于 App 模式创建模块的方式使用 Channel subscribe 功能;
- 如何基于 Module 方式使用 Channel publish 功能;
- 如何使用 local 类型的 channel 后端;
- 如何以 App 模式启动;
配置文件(cfg/local_deploy_local_ins_protobuf_channel_subscriber_app_cfg.yaml
)
aimrt:log:core_lvl: INFO # Trace/Debug/Info/Warn/Error/Fatal/Offbackends:- type: consoleexecutor:executors:- name: work_thread_pooltype: asio_threadoptions:thread_num: 4channel:backends:- type: localoptions:subscriber_use_inline_executor: falsesubscriber_executor: work_thread_poolpub_topics_options:- topic_name: "(.*)"enable_backends: [local]sub_topics_options:- topic_name: "(.*)"enable_backends: [local]module:pkgs:- path: ./libprotobuf_channel_pub_pkg.soenable_modules: [NormalPublisherModule]modules:- name: NormalPublisherModulelog_lvl: INFO- name: NormalSubscriberModulelog_lvl: INFO# Module custom configuration
NormalPublisherModule:topic_name: test_topicchannel_frq: 0.5NormalSubscriberModule:topic_name: test_topic
以app模式启动
启动命令为:
./normal_pb_chn_subscriber_app ./cfg/local_deploy_local_ins_protobuf_channel_subscriber_app_cfg.yaml
**注意:**aim_cli
工具不支持生成App模式代码框架,因此不会有相应的启动脚本,需要自行编写。
- 可以在生成代码框架时,先使用pkg模式进行设置,之后对生成的配置文件、启动脚本进行修改即可
编写一个app模式的启动脚本start_local_deploy_local_ins_protobuf_channel_publisher_app.sh
:
#!/bin/bash./normal_pb_chn_subscriber_app ./cfg/local_deploy_local_ins_protobuf_channel_subscriber_app_cfg.yaml
知识点
发布消息的流程
✅ 1. 读取配置参数(如 topic 名称、发布频率)
-
作用:动态配置 topic 名、发布频率等运行参数。
-
接口示例:
auto file_path = core_.GetConfigurator().GetConfigFilePath(); YAML::Node cfg_node = YAML::LoadFile(std::string(file_path)); topic_name_ = cfg_node["topic_name"].as<std::string>(); channel_frq_ = cfg_node["channel_frq"].as<double>();
✅ 2. 获取执行器 ExecutorRef(用于任务调度)
-
作用:用于安排主发布任务的异步执行。
-
接口:
executor_ = core_.GetExecutorManager().GetExecutor("work_thread_pool");
-
验证是否支持定时调度:
AIMRT_CHECK_ERROR_THROW(executor_ && executor_.SupportTimerSchedule(), ...);
✅ 3. 获取发布者 PublisherRef(指定 topic)
-
作用:绑定发布主题,用于后续发送消息。
-
接口:
publisher_ = core_.GetChannelHandle().GetPublisher(topic_name_);
✅ 4. 注册发布消息类型(绑定 Protobuf 类型)
-
作用:向系统注册将要发送的 Protobuf 消息类型。
-
接口:
bool ret = aimrt::channel::RegisterPublishType<Protobuf_channel::my_proto::ExampleEventMsg>(publisher_); AIMRT_CHECK_ERROR_THROW(ret, "注册发布类型失败");
✅ 5. 创建 PublisherProxy(可选,简化发布)
-
作用:封装 PublisherRef,便于发布消息,自动处理上下文。
-
接口:
aimrt::channel::PublisherProxy<Protobuf_channel::my_proto::ExampleEventMsg> publisher_proxy(publisher_);
✅ 6. 构造并填充消息对象
-
作用:设置消息字段(如文本、数值)。
-
接口:
Protobuf_channel::my_proto::ExampleEventMsg msg; msg.set_msg("当前计数: " + std::to_string(count)); msg.set_num(count);
✅ 7. 发布消息
-
作用:将消息写入通道,供订阅者消费。
-
接口:
publisher_proxy.Publish(msg);
🗂️ 总结表(含具体接口)
步骤 | 描述 | 接口/代码片段 |
---|---|---|
1 | 获取配置参数 | cfg_node["topic_name"].as[std::string](std::string)() cfg_node["channel_frq"].as<double>() |
2 | 获取执行器 | core_.GetExecutorManager().GetExecutor("work_thread_pool") |
3 | 获取 Publisher | core_.GetChannelHandle().GetPublisher(topic_name_) |
4 | 注册消息类型 | RegisterPublishType<ExampleEventMsg>(publisher_) |
5 | 创建 PublisherProxy | PublisherProxy<ExampleEventMsg> proxy(publisher_) |
6 | 构造消息 | msg.set_msg(...) ,msg.set_num(...) |
7 | 发布消息 | proxy.Publish(msg) |
接收消息的流程
✅ 1. 读取配置参数(如 topic 名称)
-
作用:动态配置接收的 topic 名,便于部署时灵活调整。
-
接口示例:
auto file_path = core_.GetConfigurator().GetConfigFilePath(); YAML::Node cfg_node = YAML::LoadFile(std::string(file_path)); topic_name_ = cfg_node["topic_name"].as<std::string>();
✅ 2. 获取订阅者 SubscriberRef(绑定到 topic)
-
作用:从通道系统获取订阅者句柄,准备订阅对应的消息通道。
-
接口:
subscriber_ = core_.GetChannelHandle().GetSubscriber(topic_name_); AIMRT_CHECK_ERROR_THROW(subscriber_, "获取主题'{}'的订阅者失败", topic_name_);
✅ 3. 注册订阅消息类型 + 绑定回调函数(完成订阅)
-
作用:声明要接收的消息类型 + 注册回调函数; 回调函数将在消息到达时被自动触发。
-
接口:
bool ret = aimrt::channel::Subscribe<Protobuf_channel::my_proto::ExampleEventMsg>(subscriber_,std::bind(&NormalSubscriberModule::EventHandle, this,std::placeholders::_1, std::placeholders::_2)); AIMRT_CHECK_ERROR_THROW(ret, "订阅消息失败");
-
说明:
- 使用的是【函数风格】接口;
- 回调形式为【带 Context 参数】+【智能指针消息】;
- 只能在模块的
Initialize()
阶段调用订阅函数; - 一个
SubscriberRef
不能重复订阅相同类型。
✅ 4. 实现回调函数,处理接收到的消息
-
作用:处理订阅消息的内容,可访问上下文信息、执行业务逻辑。
-
接口:
void NormalSubscriberModule::EventHandle(aimrt::channel::ContextRef ctx,const std::shared_ptr<const Protobuf_channel::my_proto::ExampleEventMsg>& data) {AIMRT_INFO("收到新消息,上下文: {}, 数据内容: {}", ctx.ToString(),aimrt::Pb2CompactJson(*data)); }
-
说明:
-
ctx
提供上下文信息(如消息来源、时间戳); -
data
是 Protobuf 消息的 const 智能指针,可直接使用; - 处理逻辑可以轻量,也可以将任务调度到其它执行器中(但执行器不是在订阅处指定的)。
-
🗂️ 总结表(含具体接口)
步骤 | 描述 | 接口/代码片段 |
---|---|---|
1 | 获取配置参数(topic) | cfg_node["topic_name"].as[std::string](std::string)() |
2 | 获取 SubscriberRef | core_.GetChannelHandle().GetSubscriber(topic_name_) |
3 | 注册消息回调 | Subscribe<ExampleEventMsg>(subscriber_, callback) |
4 | 实现回调处理函数 | EventHandle(ContextRef, shared_ptr<const Msg>) |
关于订阅方的执行器
❓1. 为什么订阅方在代码中没有显式指定执行器?
在 AimRT 中,订阅方执行器的配置 不在订阅模块代码中指定,而是 通过配置文件的 Channel 后端参数进行集中管理,原因如下:
- 解耦设计理念:AimRT 框架采用高度模块化设计,模块本身只声明对 Topic 的订阅关系,具体使用哪个执行器(或是否同步处理)由
channel.backends.options
统一决定; - 配置驱动、逻辑清晰:通过配置控制订阅执行行为可以更灵活地调整运行时特性,例如从同步切换为异步、调整执行线程数,而无需更改模块实现;
- 利于跨模块优化调度:在同一 Channel 后端中集中处理执行策略,有助于线程资源的统一调度和优化,避免各模块各自为政、造成资源浪费或线程争抢。
因此:订阅方是否使用执行器,由 channel.backends[].options
中的 subscriber_executor
决定,而非模块代码内部。
🛠️ 2. 相关配置解释
executor:executors:- name: work_thread_pooltype: asio_threadoptions:thread_num: 4
🔸 定义了一个线程池执行器 work_thread_pool
,基于 asio_thread
类型,拥有 4 个线程。
🔸 这个线程池将会执行消息发布循环、消息接收回调的代码
channel:backends:- type: localoptions:subscriber_use_inline_executor: falsesubscriber_executor: work_thread_pool
🔸 配置了一个 local
类型的 Channel 后端:
-
subscriber_use_inline_executor: false
:表示不使用同步执行模式; -
subscriber_executor: work_thread_pool
:所有订阅回调将异步投递到work_thread_pool
执行器中; - 因此,订阅模块的回调逻辑不会阻塞发布方的
Publish()
调用。
pub_topics_options:- topic_name: "(.*)"enable_backends: [local]sub_topics_options:- topic_name: "(.*)"enable_backends: [local]
🔸 通配符 (.*)
匹配所有 Topic,使其均由 local
后端处理。
🔸 因此可以实现不同话题使用不同后端处理
🔌 3. AimRT 插件机制
AimRT 的 Channel
通信机制是高度可扩展的 —— 其后端实现支持插件化架构。除了内置的 local
后端之外,官方或第三方还可以实现如下多种通信形式的插件:
- 网络通信(如 TCP、UDP、Http)
- 中间件集成(如 ROS2 DDS)
这些插件均可作为 channel.backends
的新类型,通过配置加载并与发布/订阅模块联动,实现模块间的远程、跨进程或跨平台消息传递。
相关内容后续学习插件章节时再进行详细记录
相关文章:
AimRT 从零到一:官方示例精讲 —— 六、pb_chn示例.md
pb_chn示例 官方仓库:pb_chn 这个官方示例展示了如何基于 protobuf 协议和 local 后端实现 channel 通信。主要包括四种场景: 基础示例:分别用两个模块(Publisher 和 Subscriber)发布和订阅 protobuf 消息ÿ…...
【Web】如何解决 `npm run dev` 报错 `address already in use 127.0.0.1:9005` 的问题
在开发过程中,我们可能会遇到端口占用的问题,尤其是当多个进程或服务尝试监听同一个端口时。最近在运行 npm run dev 时,我遇到的错误是 address already in use 127.0.0.1:9005,这让我花了些时间才找到问题的根源。本文将总结该问…...
每日一道leetcode(不会做学习版,多学一题)
2542. 最大子序列的分数 - 力扣(LeetCode) 题目 给你两个下标从 0 开始的整数数组 nums1 和 nums2 ,两者长度都是 n ,再给你一个正整数 k 。你必须从 nums1 中选一个长度为 k 的 子序列 对应的下标。 对于选择的下标 i0 &#…...
当OA闯入元宇宙:打卡、报销和会议的未来狂想
引言:虚实共生中的组织基因突变 元宇宙正以虚实共生的形态重构人类协作的底层逻辑。传统OA系统建立的物理规则——指纹打卡验证在场性、纸质票据堆砌信任链、会议室排期协调时空资源——在元宇宙的数字原野上迎来基因级重组。这场变革不仅是技术工具的迭代…...
线程数据同步的三种方式
1. 线程同步机制(用于控制线程之间的访问顺序和互斥) 互斥锁(Mutex):用于保护共享资源,确保同一时间只有一个线程可以访问。 信号量(Semaphore):用于控制多个线程对共享…...
docker拉取国内镜像
1. 场景 最近整了一个tencent云服务器,想要玩一下docker,结果发现拉不下来,镜像根本拉不下来。 2. 原因 1.云服务器无法访问外网; 2. 国内的很多公有镜像仓库都被封了; 3. 推荐 https://zhuanlan.zhihu.com/p/713…...
中阳策略下的价格趋势识别技巧
中阳策略下的价格趋势识别技巧 在快速变化的市场环境中,掌握有效的趋势识别技巧成为交易者的重要技能。"中阳"策略主张通过量价关系和趋势线分析,捕捉价格运行的主方向,提升交易成功率。 市场中,价格的上行或下行并非随…...
MIT6.S081 - Lab11 networking(网络栈)
本篇是 MIT6.S081 2020 操作系统课程 Lab11 的实验笔记,这是课程的最后一个实验了,目标是为 xv6 实现 E1000 网卡驱动的两个核心函数:发送数据包 e1000_transmit() 和接收数据包 e1000_recv()。 Lab11 地址:https://pdos.csail.mi…...
创龙全志T536全国产(4核A55 ARM+RISC-V+NPU 17路UART)工业开发板硬件说明书
前 言 本文档主要介绍TLT536-EVM评估板硬件接口资源以及设计注意事项等内容。 T536MX-CXX/T536MX-CEN2处理器的IO电平标准一般为1.8V、3.3V,上拉电源一般不超过3.3V或1.8V,当外接信号电平与IO电平不匹配时,中间需增加电平转换芯片或信号隔离芯片。按键或接口需考虑ESD设计…...
《解锁CSS Flex布局:重塑现代网页布局的底层逻辑》
网页布局作为用户体验的基石,其重要性不言而喻。从早期简单的表格布局,到后来基于浮动与定位的复杂尝试,网页布局技术始终在不断演进。而CSS Flex布局的出现,宛如一颗璀璨的新星,彻底革新了网页布局的设计理念与实践方…...
deepseek_ai_ida_plugin开源插件,用于使用 DeepSeekAI 将函数反编译并重命名为人类可读的视图。该插件仅在 ida9 上进行了测试
一、软件介绍 文末提供程序和源码下载 deepseek_ai_ida_plugin开源插件,用于使用 DeepSeekAI 将函数反编译并重命名为人类可读的视图。该插件仅在 ida9 上进行了测试。FunctionRenamerDeepseekAI.cpp 此文件包含 Hex-Rays 反编译器的主要插件实现。它反编译当前函数…...
完整的 SSL 证书生成与 Spring Boot 配置流程
一、生成 SSL 证书 目标:创建 PKCS12 格式的密钥库文件(keystore.p12),供 Spring Boot 使用。 方法 1:使用 keytool(推荐,直接生成 PKCS12 文件) 生成密钥库:keytool -genkeypair \-alias mydomain \ # 别名(自定义,如 mydomain)-keyalg RSA \ …...
taro小程序如何实现大文件(视频、图片)后台下载功能?
一、需求背景 1、需要实现小程序下载最大500M视频 2、同时需支持图片下载 3、退到其他页面再次回到当前页面时,下载进度也需要展示 二、实现步骤 1、在app.ts文件定义一个全局变量globalDownLoadData 2、写一个独立的下载hooks,代码如下(…...
阿里云bgp服务器优势有哪些?搭建bgp服务器怎么做?
阿里云服务器bgp优势有哪些?搭建bgp服务器怎么做? BGP服务器的优势 BGP(Border Gateway Protocol) 是互联网的核心路由协议,用于在不同自治系统(AS)之间交换路由信息。BGP服务器的核心优势体现在网络连接…...
java连接redis服务器
直接从 Redis 获取数据通常是 Redis通过 客户端库实现的,Jedis 是 Java 中一个常用的 Redis 客户端库。有以下两种主要方式: 1. 使用单个 Jedis 实例(不使用连接池) import redis.clients.jedis.Jedis; public class RedisExampl…...
从零开始:Android Studio开发购物车(第二个实战项目)
一年经验的全栈程序员,目前头发健在,但不知道能撑多久。 文章目录 前言 一、页面编写 1. 顶部标签栏title_shopping.xml 2. 商品展现列表activity_shopping_channel.xml 3. 商品详情页面activity_shopping_detail.xml 4. 购物车页面activity_shopping…...
2. python协程/异步编程详解
目录 1. 简单的异步程序 2. 协程函数和协程对象 3. 事件循环 4. 任务对象Task及Future对象 4.1 Task与Future的关系 4.2 Future对象 4.3 全局对象和循环事件对象 5. await关键字 6. 异步上下文管理 7.异步迭代器 8. asyncio的常用函数 8.1 asyncio.run 8.2 asyncio.get…...
XSS靶场实战(工作wuwuwu)
knoxss knoxss Single Reflection Using QUERY of URL ——01 测试标签 <script>alert(666666)</script>——02: " <h1>test</h1>没有反应,查看源码 现在需要闭合双引号,我计划还是先搞标签 "><h1>tes…...
DNA复制过程3D动画教学工具
DNA复制过程3D动画教学工具 访问工具页面: DNA复制动画演示 工具介绍 我开发了一个交互式的DNA复制过程3D动画演示工具,用于分子生物学教学。这个工具直观展示了: DNA双螺旋结构的解旋过程碱基互补配对原理半保留复制机制完整的复制周期动画 主要特点…...
在Mybatis中写sql的常量应用
下面示例把原来写死的 1、2、3 都替换成了绑定好的常量,同时额外演示了如何把第五个状态也一起统计(如果你的 DTO 没有对应字段,也可删掉相应那一行)。 <!-- 1. 定义可复用的常量绑定 --> <sql id"DeviceStatusCon…...
一次讲明白SaaS、PaaS、IaaS、aPaaS、iPaaS、RaaS、RPAaaS
在数字化浪潮与5G技术的强势驱动下,各行业对云服务的需求正呈现出井喷式增长态势,众多企业纷纷投身云服务的怀抱,以期在激烈的市场竞争中抢占先机。而谷云科技作为iPaaS领域的佼佼者,也在这股浪潮中大放异彩,助力企业实…...
RTDETRv2 pytorch训练
RTDETRv2 pytorch训练 1. 代码获取2. 数据集制作3. 环境配置4. 代码修改1)configs/dataset/coco_detection.yml2) configs/src/data/coco_dataset.py3)configs/src/core/yaml_utils.py4)configs/rtdeterv2/include/optimizer.yml 5. 代码训练…...
Unity3D仿星露谷物语开发39之非基于网格的光标
1、目标 当鼠标移动到reapable scenary(可收割庄稼)上方时,光标会变成十字架。 之前章节中,Grid有Dug/Watered属性,光标移动上方时会显示方框。 而这次的功能并非基于Grid的属性,而是基于scenary&#x…...
什么是 MCP?AI 应用的“USB-C”标准接口详解
目录 🧩 什么是 MCP?AI 应用的“USB-C”标准接口详解 📌 背景与动机 🧠 核心概念 🏗️ 技术架构 🚀 应用场景 🧩 什么是 MCP?AI 应用的“USB-C”标准接口详解 📌 背…...
狼人杀中的智能策略:解析AI如何理解复杂社交游戏
想要掌握如何将大模型的力量发挥到极致吗?叶梓老师带您深入了解 Llama Factory —— 一款革命性的大模型微调工具(限时免费)。 1小时实战课程,您将学习到如何轻松上手并有效利用 Llama Factory 来微调您的模型,以发挥其…...
10 基于Gazebo和Rviz实现导航仿真,包括SLAM建图,地图服务,机器人定位,路径规划
在9中我们已经实现了机器人的模块仿真,现在要在这个基础上实现SLAM建图,地图服务,机器人定位,路径规划 1. 还是在上述机器人的工作空间下,新建功能包(nav),导入依赖 gmapping ma…...
jmeter-Beashell获取请求body data
在使用JMeter的BeanShell处理器或BeanShell断言中获取HTTP请求的body数据,可以通过几种方式实现。下面是一些常用的方法: 方法1:使用prev变量 在BeanShell处理器或断言中,prev变量可以用来访问最近的sampler(采样器&…...
区块链密码学核心
文章目录 概要1. 基础密码学哈希函数(Hash Function)对称加密与非对称加密数字签名(Digital Signature)密钥管理 2. 区块链专用密码学技术零知识证明(Zero-Knowledge Proof, ZKP)同态加密(Homom…...
Git 多账号切换及全局用户名设置不生效问,GIT进行上传无权限问题
解决 Git 多账号切换及全局用户名设置不生效问题 在软件开发过程中,我们经常会使用 Git 进行版本控制。有时,我们需要在同一台机器上管理多个 Git 账号,最近我在进行使用git的时候因为项目要进行上传的不同的git账号,但是通过本地…...
阿里云服务迁移实战: 04-IP 迁移
普通过户 如资料过户按量付费EIP所述,如果原账号是个人账号,则目标账号无限制,如果原账号是企业账号,则目标账号必须为相同认证主体的企业账号。 其主要操作就是,在原账号发起过户,在新账号接收过户。具体…...
探索PyTorch中的空间与通道双重注意力机制:实现concise的scSE模块
探索PyTorch中的空间与通道双重注意力机制:实现concise的scSE模块 在深度学习领域,尤其是在计算机视觉任务中,特征图的注意力机制变得越来越重要。近期,我在研究一种结合了通道和空间两种注意力机制的模块——Concise Spatial an…...
关闭正点原子atk-qtapp-start.service
# 查找相关服务 systemctl list-units --typeservice --staterunning # 查看详细信息 systemctl status atk-qtapp-start.service >> ● atk-qtapp-start.service - Qt App Start …...
[按键安卓ios脚本辅助插件开发]数组排序函数例子
按键安卓ios工具辅助脚本插件开发教程,教程目的是让大家掌握Lua基本语法与按键精灵手机版的插件开发制作。 在按键精灵中排序需要我们自己写算法实现,例如快速排序,冒泡排序等,而在Lua中有内置的table.sort()排序命令。 这个命令…...
【BotSharp框架示例 ——实现聊天机器人,并通过 DeepSeek V3实现 function calling】
BotSharp框架示例 ——实现聊天机器人,并通过 DeepSeek V3实现 function calling 一、一点点感悟二、创建项目1、创建项目2、添加引用3、MyWeatherPlugin项目代码编写4、WeatherApiDefaultService项目代码编写5、WebAPI MyWeatherAPI 的项目代码编写6、data文件夹中…...
记录一个单独读取evt.bdf的方法
问题描述 之前只能使用eeglab的工具,读取博瑞康达的data.bdf和evt.bdf时,使用的是eeglab的下面这个读取文件的插件。 evt.bdf使用记事本文件查看是乱码的形式。 实现方法 事实上,我们可以直接对这个文件的16进制进行解析。 对文件的位和…...
是否想要一个桌面哆啦A梦的宠物
是否想拥有一个在指定时间喊你的桌面宠物呢(手动狗头) 如果你有更好的想法,欢迎提出你的想法。 是否考虑过跟开发者一对一,提出你的建议(狗头)。 https://wwxc.lanzouo.com/idKnJ2uvq11c 密码:bbkm...
防爆风扇储能轴流风机风量风压如何保障通风安全?
在化工车间、煤矿巷道等高危环境中,通风安全是保障生产与人员生命安全的关键防线。防爆风扇储能轴流风机凭借独特的风量风压设计与性能优势,成为守护通风安全的可靠屏障。那么,它究竟是如何发挥作用的呢? 从风量设计来看,防爆风…...
Centos 7系统 宝塔部署Tomcat项目(保姆级教程)
再看文章之前默认已经安装好系统,可能是云系统,或者是虚拟机。 宝塔安装 这个比较简单,参考这个老哥的即可: https://blog.csdn.net/weixin_42753193/article/details/125959289 环境配置 进入宝塔面板之后会出现环境安装&…...
Electron读取本地文件
在 Electron 应用中,可以使用 Node.js 的 fs 模块来读取本地文件。以下是如何实现这一功能的详细步骤。 1. 设置项目结构 假设你的项目目录如下: my-electron-app/ ├── main.js ├── index.html └── renderer.js2. 使用 fs 模块读取文件 在主…...
Plesk 下的 IP 地址管理
Plesk是一个方便管理的控制面板,可以简化网站主机和服务器数据中心的自动化管理。它专为提供Windows和Linux服务器的供应商设计。Plesk面板适用于虚拟主机和独立服务器 服务器管理员可以使用Plesk来配置新网站、电子邮件系统和转售商账户,也可以通过Ple…...
基于STM32、HAL库的DS28E15P安全验证及加密芯片驱动程序设计
一、简介: DS28E15P是Maxim Integrated (现为Analog Devices)生产的一款1-Wire EEPROM芯片,具有以下特点: 1-Wire接口通信,仅需单根数据线加地线 1024位(128字节)EEPROM存储器 每个器件具有唯一的64位ROM ID 工作电压范围:2.8V至5.25V 内置CRC16校验功能 可编程写保护功能…...
浅析localhost、127.0.0.1 和 0.0.0.0的区别
文章目录 三者的解释三者的核心区别总结使用场景示例什么是回环地址常见问题开发工具中的地址使用为什么开发工具同时支持localhost和127.0.0.1?实际应用示例VSCode中的Live Server插件VSCode中的VUE项目IDEA中的Spring Boot应用 最佳实践建议 localhost、 127.0.0…...
antd+react实现html图片预览效果
import { Image } from ‘antd’; import { useEffect, useRef, useState } from ‘react’; import styles from ‘./index.module.less’; interface PreviewHtmlWithImagesProps { htmlContent: string; } const PreviewHtmlWithImages: React.FC ({ htmlContent }) >…...
【React】轻松掌握 React 中 useEffect的使用
你有没有想过,为什么你的 React 组件能够轻松应对周围发生的变化,比如每当有新数据到来时自动更新,或者处理可以动态响应实时事件的组件?这就是 useEffect 的用武之地!这个强大的钩子(Hook)就像…...
请简述一下什么是 Kotlin?它有哪些特性?
1 JVM 语言的共性:编译成字节码文件 Kotlin 和 Java 同属于 JVM(Java Virtual Machine)语言,它们的代码最终都会被编译成 JVM 字节码(.class)文件。 编译流程: Kotlin 编译:Kotli…...
Post与Get以及@Requestbody和@Pathvariable标签的应用
Post的使用场景:简单来讲适用于有安全性限制的,因为post请求的内容会被存在某个封装内容中(比如表单、jason格式等),这部分内容是不会被浏览器的cache所捕捉,安全性较强。 Get的使用场景:与pos…...
基于tabula对pdf中的excel进行识别并转换成word(三)
上一节中是基于PaddleOCR对图片中的excel进行识别并转换成word优化,本节改变思路,直接从pdf中读取表格的信息,具体思路如下所述。 PDF中的表格数据如下截图所示: 一、基于tabula从PDF中提取表格 df_list tabula.read_pdf("…...
k8s集群环境部署业务系统
k8s集群环境部署业务系统,通过shell脚本整合部署过程,简化部署流程。操作流程如下: A,B为业务系统服务名。 一.部署前准备。在k8s集群各节点执行该脚本,完成业务系统镜像加载。 #!/bin/bash # 1.删除deployment ech…...
MySQL 8.4.4 安全升级指南:从漏洞修复到版本升级全流程解析
目录 二、升级前关键注意事项 1. 数据安全与备份 2. 版本兼容性与路径规划 三、分步升级操作流程 1. 环境预检与准备 2. 安装包部署 3. 强制升级组件 4. 验证与启动 一、背景与必要性 近期安全扫描发现生产环境的 MySQL 数据库存在多个高危漏洞(CVE 详情参见Oracle 官…...
“假读“操作在I2C接收流程中的原因
在I2C接收流程中,"假读"操作是NXP I2C控制器工作特性要求的必要操作,具体原因如下: // 接收函数关键代码 void i2c_master_read(I2C_Type *base, unsigned char *buf, unsigned int size) {// ...dummy base->I2DR; /* 假读 *…...