6.2 C++接口使用示例
本章节将带您实现索引所示若干功能
构建 & 运行说明
进入到解压后的 sdk 顶层目录,执行以下命令
source /opt/ros/humble/setup.bash colcon build source install/setup.bash ros2 run examples '对应功能名称如: get_mc_action'
📝 代码说明
完整的代码实现包含完整的错误处理、信号处理、超时处理等机制,确保程序的健壮性。请您在 examples 目录下查看/修改
小心
ROS的服务(service)机制在跨板通信时存在一些待优化问题, 二次开发时请参考例程添加异常处理、快速重试等保护机制
6.2.1 获取机器人模式
通过调用GetMcAction服务获取机器人当前的运行模式,包括名称和状态信息。
1#include "aimdk_msgs/srv/get_mc_action.hpp"
2#include "aimdk_msgs/msg/common_request.hpp"
3#include "aimdk_msgs/msg/response_header.hpp"
4#include "rclcpp/rclcpp.hpp"
5#include <chrono>
6#include <memory>
7#include <signal.h>
8
9// Global variable used for signal handling
10std::shared_ptr<rclcpp::Node> g_node = nullptr;
11
12// Signal handler function
13void signal_handler(int signal) {
14 if (g_node) {
15 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
16 signal);
17 g_node.reset();
18 }
19 rclcpp::shutdown();
20 exit(signal);
21}
22
23class GetMcActionClient : public rclcpp::Node {
24public:
25 GetMcActionClient() : Node("get_mc_action_client") {
26
27 client_ = this->create_client<aimdk_msgs::srv::GetMcAction>(
28 "/aimdk_5Fmsgs/srv/GetMcAction"); // correct the service path
29 RCLCPP_INFO(this->get_logger(), "✅ GetMcAction client node created.");
30
31 while (!client_->wait_for_service(std::chrono::seconds(2))) {
32 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
33 }
34 RCLCPP_INFO(this->get_logger(),
35 "🟢 Service available, ready to send request.");
36 }
37
38 void send_request() {
39 try {
40 auto request = std::make_shared<aimdk_msgs::srv::GetMcAction::Request>();
41 request->request = aimdk_msgs::msg::CommonRequest();
42
43 RCLCPP_INFO(this->get_logger(), "📨 Sending request to get robot mode");
44
45 // Set a service call timeout
46 const std::chrono::milliseconds timeout(250);
47 for (int i = 0; i < 8; i++) {
48 request->request.header.stamp = this->now();
49 auto future = client_->async_send_request(request);
50 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
51 future, timeout);
52 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
53 // retry as remote peer is NOT handled well by ROS
54 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
55 continue;
56 }
57 // future.done
58 auto response = future.get();
59 RCLCPP_INFO(this->get_logger(), "✅ Robot mode get successfully.");
60 RCLCPP_INFO(this->get_logger(), "Mode name: %s",
61 response->info.action_desc.c_str());
62 RCLCPP_INFO(this->get_logger(), "Mode status: %d",
63 response->info.status.value);
64 return;
65 }
66 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
67 } catch (const std::exception &e) {
68 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
69 }
70 }
71
72private:
73 rclcpp::Client<aimdk_msgs::srv::GetMcAction>::SharedPtr client_;
74};
75
76int main(int argc, char *argv[]) {
77 try {
78 rclcpp::init(argc, argv);
79
80 // Set up signal handlers
81 signal(SIGINT, signal_handler);
82 signal(SIGTERM, signal_handler);
83
84 // Create node
85 g_node = std::make_shared<GetMcActionClient>();
86 auto client = std::dynamic_pointer_cast<GetMcActionClient>(g_node);
87 if (client) {
88 client->send_request();
89 }
90
91 // Clean up resources
92 g_node.reset();
93 rclcpp::shutdown();
94
95 return 0;
96 } catch (const std::exception &e) {
97 RCLCPP_ERROR(rclcpp::get_logger("main"),
98 "Program exited with exception: %s", e.what());
99 return 1;
100 }
101}
使用说明
ros2 run examples get_mc_action
输出示例
...
[INFO] [1764066631.021247791] [get_mc_action_client]: Current robot mode:
[INFO] [1764066631.021832667] [get_mc_action_client]: Mode name: PASSIVE_DEFAULT
[INFO] [1764066631.022396136] [get_mc_action_client]: Mode status: 100
接口参考
服务:
/aimdk_5Fmsgs/srv/GetMcAction消息:
aimdk_msgs/srv/GetMcAction
6.2.2 设置机器人模式
该示例中用到了SetMcAction服务,运行节点程序后终端输入模式对应的字段值,机器人会尝试进入相应的运动模式。
切入稳定站立(STAND_DEFAULT)模式前,确保机器人已立起且双脚着地。
模式切换需遵循模式转换图, 跨模式转换不能成功
走跑模式(LOCOMOTION_DEFAULT)和稳定站立模式一体化自动切换,按模式转换图流转到两者中较近的即可
1#include "aimdk_msgs/srv/set_mc_action.hpp"
2#include "aimdk_msgs/msg/common_response.hpp"
3#include "aimdk_msgs/msg/common_state.hpp"
4#include "aimdk_msgs/msg/mc_action.hpp"
5#include "aimdk_msgs/msg/mc_action_command.hpp"
6#include "aimdk_msgs/msg/request_header.hpp"
7#include "rclcpp/rclcpp.hpp"
8#include <chrono>
9#include <iomanip>
10#include <memory>
11#include <signal.h>
12#include <unordered_map>
13#include <vector>
14
15// Global variable used for signal handling
16std::shared_ptr<rclcpp::Node> g_node = nullptr;
17
18// Signal handler function
19void signal_handler(int signal) {
20 if (g_node) {
21 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
22 signal);
23 g_node.reset();
24 }
25 rclcpp::shutdown();
26 exit(signal);
27}
28
29class SetMcActionClient : public rclcpp::Node {
30public:
31 SetMcActionClient() : Node("set_mc_action_client") {
32
33 client_ = this->create_client<aimdk_msgs::srv::SetMcAction>(
34 "/aimdk_5Fmsgs/srv/SetMcAction");
35 RCLCPP_INFO(this->get_logger(), "✅ SetMcAction client node created.");
36
37 // Wait for the service to become available
38 while (!client_->wait_for_service(std::chrono::seconds(2))) {
39 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
40 }
41 RCLCPP_INFO(this->get_logger(),
42 "🟢 Service available, ready to send request.");
43 }
44
45 bool send_request(std::string &action_name) {
46 try {
47 auto request = std::make_shared<aimdk_msgs::srv::SetMcAction::Request>();
48 request->header = aimdk_msgs::msg::RequestHeader();
49
50 // Set robot mode
51 aimdk_msgs::msg::McActionCommand command;
52 command.action_desc = action_name;
53 request->command = command;
54
55 RCLCPP_INFO(this->get_logger(), "📨 Sending request to set robot mode: %s",
56 action_name.c_str());
57
58 // Set Service Call Timeout
59 const std::chrono::milliseconds timeout(250);
60 for (int i = 0; i < 8; i++) {
61 request->header.stamp = this->now();
62 auto future = client_->async_send_request(request);
63 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
64 future, timeout);
65 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
66 // retry as remote peer is NOT handled well by ROS
67 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
68 continue;
69 }
70 // future.done
71 auto response = future.get();
72 if (response->response.status.value ==
73 aimdk_msgs::msg::CommonState::SUCCESS) {
74 RCLCPP_INFO(this->get_logger(), "✅ Robot mode set successfully.");
75 return true;
76 } else {
77 RCLCPP_ERROR(this->get_logger(), "❌ Failed to set robot mode: %s",
78 response->response.message.c_str());
79 return false;
80 }
81 }
82 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
83 return false;
84 } catch (const std::exception &e) {
85 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
86 return false;
87 }
88 }
89
90private:
91 rclcpp::Client<aimdk_msgs::srv::SetMcAction>::SharedPtr client_;
92};
93
94static std::unordered_map<std::string, std::vector<std::string>> g_action_info =
95 {
96 {"PASSIVE_DEFAULT", {"PD", "joints with zero torque"}},
97 {"DAMPING_DEFAULT", {"DD", "joints in damping mode"}},
98 {"JOINT_DEFAULT", {"JD", "Position Control Stand (joints locked)"}},
99 {"STAND_DEFAULT", {"SD", "Stable Stand (auto-balance)"}},
100 {"LOCOMOTION_DEFAULT", {"LD", "locomotion mode (walk or run)"}},
101};
102
103int main(int argc, char *argv[]) {
104 try {
105 rclcpp::init(argc, argv);
106
107 // Set up signal handlers
108 signal(SIGINT, signal_handler);
109 signal(SIGTERM, signal_handler);
110
111 // Create node
112 g_node = std::make_shared<SetMcActionClient>();
113 auto client = std::dynamic_pointer_cast<SetMcActionClient>(g_node);
114
115 if (client) {
116 std::unordered_map<std::string, std::string> choices;
117 std::string motion;
118
119 // Prefer command-line argument; otherwise prompt user
120 if (argc > 1) {
121 motion = argv[1];
122 RCLCPP_INFO(g_node->get_logger(),
123 "Using abbr of motion mode from cmdline: %s", argv[1]);
124 } else {
125 std::cout << std::left << std::setw(4) << "abbr"
126 << " - " << std::setw(20) << "robot mode"
127 << " : "
128 << "description" << std::endl;
129 for (auto &it : g_action_info) {
130 std::cout << std::left << std::setw(4) << it.second[0] << " - "
131 << std::setw(20) << it.first << " : " << it.second[1]
132 << std::endl;
133 }
134 std::cout << "Enter abbr of motion mode:";
135 std::cin >> motion;
136 }
137 for (auto &it : g_action_info) {
138 choices[it.second[0]] = it.first;
139 }
140
141 auto m = choices.find(motion);
142 if (m != choices.end()) {
143 auto &action_name = m->second;
144 client->send_request(action_name);
145 } else {
146 RCLCPP_ERROR(g_node->get_logger(), "Invalid abbr of robot mode: %s",
147 motion.c_str());
148 }
149 }
150
151 // Clean up resources
152 g_node.reset();
153 rclcpp::shutdown();
154
155 return 0;
156 } catch (const std::exception &e) {
157 RCLCPP_ERROR(rclcpp::get_logger("main"),
158 "Program exited with exception: %s", e.what());
159 return 1;
160 }
161}
使用说明
# 使用命令行参数设置模式(推荐)
ros2 run examples set_mc_action JD # 零力矩模式>>站姿预备(位控站立)
ros2 run examples set_mc_action SD # 机器人已立起且脚着地后方可执行,站姿预备>>稳定站立
# 稳定站立>>走跑模式 自动切换,无需手动切换
# 或者不带参数运行,程序会提示用户输入双字母缩写代码
ros2 run examples set_mc_action
输出示例
...
[INFO] [1764066567.502968540] [set_mc_action_client]: ✅ Robot mode set successfully.
注意事项
切入
STAND_DEFAULT模式前,确保机器人脚已经着地模式切换可能需要几秒钟时间完成
接口参考
服务:
/aimdk_5Fmsgs/srv/SetMcAction消息:
aimdk_msgs/srv/SetMcAction
6.2.3 设置机器人动作
该示例中用到了preset_motion_client,进入稳定站立模式后,启动节点程序,输入相应的字段值可实现左手(右手)的握手(举手、挥手、飞吻)动作。
可供调用的参数见预设动作表
1#include "aimdk_msgs/msg/common_response.hpp"
2#include "aimdk_msgs/msg/common_state.hpp"
3#include "aimdk_msgs/msg/common_task_response.hpp"
4#include "aimdk_msgs/msg/mc_control_area.hpp"
5#include "aimdk_msgs/msg/mc_preset_motion.hpp"
6#include "aimdk_msgs/msg/request_header.hpp"
7#include "aimdk_msgs/srv/set_mc_preset_motion.hpp"
8#include "rclcpp/rclcpp.hpp"
9#include <chrono>
10#include <memory>
11#include <signal.h>
12
13std::shared_ptr<rclcpp::Node> g_node = nullptr;
14
15void signal_handler(int signal) {
16 if (g_node) {
17 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
18 signal);
19 g_node.reset();
20 }
21 rclcpp::shutdown();
22 exit(signal);
23}
24
25class PresetMotionClient : public rclcpp::Node {
26public:
27 PresetMotionClient() : Node("preset_motion_client") {
28 const std::chrono::seconds timeout(8);
29
30 client_ = this->create_client<aimdk_msgs::srv::SetMcPresetMotion>(
31 "/aimdk_5Fmsgs/srv/SetMcPresetMotion");
32
33 RCLCPP_INFO(this->get_logger(), "✅ SetMcPresetMotion client node created.");
34
35 // Wait for the service to become available
36 while (!client_->wait_for_service(std::chrono::seconds(2))) {
37 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
38 }
39 RCLCPP_INFO(this->get_logger(),
40 "🟢 Service available, ready to send request.");
41 }
42
43 bool send_request(int area_id, int motion_id) {
44 try {
45 auto request =
46 std::make_shared<aimdk_msgs::srv::SetMcPresetMotion::Request>();
47 request->header = aimdk_msgs::msg::RequestHeader();
48
49 aimdk_msgs::msg::McPresetMotion motion;
50 aimdk_msgs::msg::McControlArea area;
51
52 motion.value = motion_id; // Preset motion ID
53 area.value = area_id; // Control area ID
54 request->motion = motion;
55 request->area = area;
56 request->interrupt = false; // Not interrupt current motion
57
58 RCLCPP_INFO(this->get_logger(),
59 "📨 Sending request to set preset motion: motion=%d, area=%d",
60 motion_id, area_id);
61
62 const std::chrono::milliseconds timeout(250);
63 for (int i = 0; i < 8; i++) {
64 request->header.stamp = this->now();
65 auto future = client_->async_send_request(request);
66 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
67 future, timeout);
68 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
69 // retry as remote peer is NOT handled well by ROS
70 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
71 continue;
72 }
73 // future.done
74 auto response = future.get();
75 if (response->response.header.code == 0) {
76 RCLCPP_INFO(this->get_logger(),
77 "✅ Preset motion set successfully: %lu",
78 response->response.task_id);
79 return true;
80 } else if (response->response.state.value ==
81 aimdk_msgs::msg::CommonState::RUNNING) {
82 RCLCPP_INFO(this->get_logger(), "⏳ Preset motion executing: %lu",
83 response->response.task_id);
84 return true;
85 } else {
86 RCLCPP_WARN(this->get_logger(), "❌ Failed to set preset motion: %lu",
87 response->response.task_id);
88 return false;
89 }
90 }
91 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
92 return false;
93 } catch (const std::exception &e) {
94 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
95 return false;
96 }
97 }
98
99private:
100 rclcpp::Client<aimdk_msgs::srv::SetMcPresetMotion>::SharedPtr client_;
101};
102
103int main(int argc, char *argv[]) {
104 try {
105 rclcpp::init(argc, argv);
106 signal(SIGINT, signal_handler);
107 signal(SIGTERM, signal_handler);
108
109 g_node = std::make_shared<PresetMotionClient>();
110 // Cast g_node (std::shared_ptr<rclcpp::Node>) to a derived
111 // PresetMotionClient pointer (std::shared_ptr<PresetMotionClient>)
112 auto client = std::dynamic_pointer_cast<PresetMotionClient>(g_node);
113
114 int area = 1;
115 int motion = 1003;
116 std::cout << "Enter arm area ID (1-left, 2-right): ";
117 std::cin >> area;
118 std::cout
119 << "Enter preset motion ID (1001-raise, 1002-wave, 1003-handshake, "
120 "1004-airkiss): ";
121 std::cin >> motion;
122 if (client) {
123 client->send_request(area, motion);
124 }
125
126 // Clean up resources
127 g_node.reset();
128 rclcpp::shutdown();
129
130 return 0;
131 } catch (const std::exception &e) {
132 RCLCPP_ERROR(rclcpp::get_logger("main"),
133 "Program exited with exception: %s", e.what());
134 return 1;
135 }
136}
6.2.4 夹爪控制
该示例中用到了hand_control,通过往话题/aima/hal/joint/hand/command发布消息来控制夹爪的运动
注意
注意⚠️ :运行本示例前需要在机器人运控计算单元(PC1)使用aima em stop-app mc关闭原生运控模块,获取机器人控制权。注意确保机器人安全
1#include "aimdk_msgs/msg/hand_command.hpp"
2#include "aimdk_msgs/msg/hand_command_array.hpp"
3#include "aimdk_msgs/msg/hand_type.hpp"
4#include "aimdk_msgs/msg/message_header.hpp"
5#include "rclcpp/rclcpp.hpp"
6#include <chrono>
7#include <vector>
8
9class HandControl : public rclcpp::Node {
10public:
11 HandControl()
12 : Node("hand_control"), position_pairs_({
13 {1.0, 1.0},
14 {0.0, 0.0},
15 {0.5, 0.5},
16 {0.2, 0.8},
17 {0.7, 0.3},
18 }),
19 current_index_(0) {
20 publisher_ = this->create_publisher<aimdk_msgs::msg::HandCommandArray>(
21 "/aima/hal/joint/hand/command", 10);
22
23 timer_ = this->create_wall_timer(
24 std::chrono::milliseconds(20), // 50Hz
25 std::bind(&HandControl::publish_hand_commands, this));
26
27 last_switch_time_ = now();
28 RCLCPP_INFO(this->get_logger(), "The hand control node has been started!");
29 }
30
31 void publish_hand_commands() {
32 // 1. Determine if it's time to switch parameters.
33 auto now_time = this->now();
34 if ((now_time - last_switch_time_).seconds() >= 2.0) {
35 current_index_ = (current_index_ + 1) % position_pairs_.size();
36 last_switch_time_ = now_time;
37 RCLCPP_INFO(this->get_logger(),
38 "Switched to the next parameter group, index=%zu (left=%.2f, "
39 "right=%.2f)",
40 current_index_, position_pairs_[current_index_].first,
41 position_pairs_[current_index_].second);
42 }
43
44 auto msg = std::make_unique<aimdk_msgs::msg::HandCommandArray>();
45 msg->header = aimdk_msgs::msg::MessageHeader();
46
47 float left_position = position_pairs_[current_index_].first;
48 float right_position = position_pairs_[current_index_].second;
49
50 aimdk_msgs::msg::HandCommand left_hands;
51 left_hands.name = "left_hand";
52 left_hands.position = left_position;
53 left_hands.velocity = 1.0;
54 left_hands.acceleration = 1.0;
55 left_hands.deceleration = 1.0;
56 left_hands.effort = 1.0;
57
58 aimdk_msgs::msg::HandCommand right_hands;
59 right_hands.name = "right_hand";
60 right_hands.position = right_position;
61 right_hands.velocity = 1.0;
62 right_hands.acceleration = 1.0;
63 right_hands.deceleration = 1.0;
64 right_hands.effort = 1.0;
65
66 msg->left_hands.push_back(left_hands);
67 msg->right_hands.push_back(right_hands);
68 msg->left_hand_type.value = 2;
69 msg->right_hand_type.value = 2;
70
71 publisher_->publish(std::move(msg));
72 }
73
74private:
75 rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
76 rclcpp::TimerBase::SharedPtr timer_;
77
78 std::vector<std::pair<float, float>> position_pairs_;
79 size_t current_index_;
80
81 rclcpp::Time last_switch_time_;
82};
83
84int main(int argc, char *argv[]) {
85 rclcpp::init(argc, argv);
86 auto hand_control_node = std::make_shared<HandControl>();
87 rclcpp::spin(hand_control_node);
88 rclcpp::shutdown();
89 return 0;
90}
6.2.5 灵巧手控制
该示例中用到了omnihand_control,通过往话题/aima/hal/joint/hand/command发布消息来控制灵巧手的运动
注意
注意⚠️ :运行本示例前需要在机器人运控计算单元(PC1)使用aima em stop-app mc关闭原生运控模块,获取机器人控制权。注意确保机器人安全
1#include <aimdk_msgs/msg/hand_command_array.hpp>
2#include <chrono>
3#include <cmath>
4#include <memory>
5#include <rclcpp/rclcpp.hpp>
6
7using namespace std::chrono_literals;
8
9class HandCommandPublisher : public rclcpp::Node {
10public:
11 HandCommandPublisher() : Node("hand_command_publisher") {
12 publisher_ = this->create_publisher<aimdk_msgs::msg::HandCommandArray>(
13 "/aima/hal/joint/hand/command", 10);
14
15 // Create a timer to publish once per second
16 timer_ = this->create_wall_timer(
17 1s, std::bind(&HandCommandPublisher::publish_command, this));
18
19 // Create a timer to publish once per second
20 int target_finger = 0;
21 int step_ = 1;
22 bool increasing_ = true;
23 }
24
25private:
26 void publish_command() {
27 auto message = aimdk_msgs::msg::HandCommandArray();
28
29 // Set hander
30 message.header.stamp = this->now();
31 message.header.frame_id = "hand_command";
32
33 // Set the hand type
34 message.left_hand_type.value = 1; // NIMBLE_HANDS
35 message.right_hand_type.value = 1; // NIMBLE_HANDS
36
37 // Create left hand command array
38 message.left_hands.resize(10);
39
40 // Set left thumb
41 message.left_hands[0].name = "left_thumb";
42 message.left_hands[0].position = 0.0;
43 message.left_hands[0].velocity = 0.1;
44 message.left_hands[0].acceleration = 0.0;
45 message.left_hands[0].deceleration = 0.0;
46 message.left_hands[0].effort = 0.0;
47 // Set other left fingers
48 for (int i = 1; i < 10; i++) {
49 message.left_hands[i].name = "left_index";
50 message.left_hands[i].position = 0.0;
51 message.left_hands[i].velocity = 0.1;
52 message.left_hands[i].acceleration = 0.0;
53 message.left_hands[i].deceleration = 0.0;
54 message.left_hands[i].effort = 0.0;
55 }
56
57 // Create right hand command array
58 message.right_hands.resize(10);
59
60 // Set right thumb
61 message.right_hands[0].name = "right_thumb";
62 message.right_hands[0].position = 0.0;
63 message.right_hands[0].velocity = 0.1;
64 message.right_hands[0].acceleration = 0.0;
65 message.right_hands[0].deceleration = 0.0;
66 message.right_hands[0].effort = 0.0;
67
68 // Set other right fingers (pinky)
69 for (int i = 1; i < 10; i++) {
70 message.right_hands[i].name = "right_pinky";
71 message.right_hands[i].position = 0.0;
72 message.right_hands[i].velocity = 0.1;
73 message.right_hands[i].acceleration = 0.0;
74 message.right_hands[i].deceleration = 0.0;
75 message.right_hands[i].effort = 0.0;
76 }
77
78 if (target_finger <= 10) {
79 message.right_hands[target_finger].position = 0.8;
80 } else {
81 int target_finger_ = target_finger - 10;
82 double target_position = 0.8;
83 if (target_finger_ < 3) {
84 // The three thumb motors on the left hand need their signs inverted to
85 // mirror the right hand's motion
86 target_position = -target_position;
87 }
88 message.left_hands[target_finger_].position = target_position;
89 }
90
91 // Publish the message
92 publisher_->publish(message);
93
94 RCLCPP_INFO(this->get_logger(),
95 "Published hand command with target_finger: %d", target_finger);
96
97 update_target_finger();
98 }
99
100 void update_target_finger() {
101 if (increasing_) {
102 target_finger += step_;
103 if (target_finger >= 19) {
104 target_finger = 19;
105 increasing_ = false;
106 }
107 } else {
108 target_finger -= step_;
109 if (target_finger <= 0) {
110 target_finger = 0;
111 increasing_ = true;
112 }
113 }
114 }
115
116 rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
117 rclcpp::TimerBase::SharedPtr timer_;
118
119 int target_finger = 0;
120 int step_ = 1;
121 bool increasing_ = true;
122};
123
124int main(int argc, char **argv) {
125 rclcpp::init(argc, argv);
126 auto node = std::make_shared<HandCommandPublisher>();
127 rclcpp::spin(node);
128 rclcpp::shutdown();
129 return 0;
130}
6.2.6 注册二开输入源
对于v0.7之后的版本,在实现对MC的控制前,需要先注册输入源。该示例中通过/aimdk_5Fmsgs/srv/SetMcInputSource服务来注册二开的输入源,让MC感知到,只有注册过输入源后才能实现机器人的速度控制
1#include "aimdk_msgs/srv/set_mc_input_source.hpp"
2#include "aimdk_msgs/msg/common_request.hpp"
3#include "aimdk_msgs/msg/common_response.hpp"
4#include "aimdk_msgs/msg/common_state.hpp"
5#include "aimdk_msgs/msg/common_task_response.hpp"
6#include "aimdk_msgs/msg/mc_input_action.hpp"
7#include "rclcpp/rclcpp.hpp"
8#include <chrono>
9#include <memory>
10#include <signal.h>
11
12std::shared_ptr<rclcpp::Node> g_node = nullptr;
13
14void signal_handler(int signal) {
15 if (g_node) {
16 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
17 signal);
18 g_node.reset();
19 }
20 rclcpp::shutdown();
21 exit(signal);
22}
23
24class McInputClient : public rclcpp::Node {
25public:
26 McInputClient() : Node("set_mc_input_source_client") {
27 client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
28 "/aimdk_5Fmsgs/srv/SetMcInputSource");
29
30 RCLCPP_INFO(this->get_logger(), "✅ SetMcInputSource client node created.");
31
32 // Wait for the service to become available
33 while (!client_->wait_for_service(std::chrono::seconds(2))) {
34 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
35 }
36 RCLCPP_INFO(this->get_logger(),
37 "🟢 Service available, ready to send request.");
38 }
39
40 bool send_request() {
41 try {
42 auto request =
43 std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
44
45 // Set request data
46 request->action.value = 1001; // Add new input source
47 request->input_source.name = "node"; // Set message source
48 request->input_source.priority = 40; // Set priority
49 request->input_source.timeout = 1000; // Set timeout (ms)
50
51 RCLCPP_INFO(this->get_logger(), "📨 Sending input source request: (ID=%d)",
52 request->action.value);
53
54 auto timeout = std::chrono::milliseconds(250);
55 for (int i = 0; i < 8; i++) {
56 // Set header timestamp
57 request->request.header.stamp = this->now(); // use Node::now()
58 auto future = client_->async_send_request(request);
59 auto retcode = rclcpp::spin_until_future_complete(
60 this->shared_from_this(), future, timeout);
61 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
62 // retry as remote peer is NOT handled well by ROS
63 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
64 continue;
65 }
66 // future.done
67 auto response = future.get();
68 auto code = response->response.header.code;
69 if (code == 0) {
70 RCLCPP_INFO(this->get_logger(),
71 "✅ Input source set successfully. task_id=%lu",
72 response->response.task_id);
73 return true;
74 } else {
75 RCLCPP_ERROR(
76 this->get_logger(),
77 "❌ Input source set failed. ret_code=%ld, task_id=%lu "
78 "(duplicated ADD? or MODIFY/ENABLE/DISABLE for unknown source?)",
79 code, response->response.task_id);
80 return false;
81 }
82 }
83 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
84 return false;
85 } catch (const std::exception &e) {
86 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
87 return false;
88 }
89 }
90
91private:
92 rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr client_;
93};
94
95int main(int argc, char *argv[]) {
96 try {
97 rclcpp::init(argc, argv);
98 signal(SIGINT, signal_handler);
99 signal(SIGTERM, signal_handler);
100
101 g_node = std::make_shared<McInputClient>();
102 auto client = std::dynamic_pointer_cast<McInputClient>(g_node);
103
104 if (client) {
105 client->send_request();
106 }
107
108 g_node.reset();
109 rclcpp::shutdown();
110
111 return 0;
112 } catch (const std::exception &e) {
113 RCLCPP_ERROR(rclcpp::get_logger("main"),
114 "Program terminated with exception: %s", e.what());
115 return 1;
116 }
117}
6.2.7 获取当前输入源
该示例中用到了GetCurrentInputSource服务,用于获取当前已注册的输入源信息,包括输入源名称、优先级和超时时间等信息。
1#include "aimdk_msgs/srv/get_current_input_source.hpp"
2#include "aimdk_msgs/msg/common_request.hpp"
3#include "aimdk_msgs/msg/response_header.hpp"
4#include "rclcpp/rclcpp.hpp"
5#include <chrono>
6#include <memory>
7#include <signal.h>
8
9// Global node object
10std::shared_ptr<rclcpp::Node> g_node = nullptr;
11
12// Signal handler
13void signal_handler(int signal) {
14 if (g_node) {
15 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
16 signal);
17 g_node.reset();
18 }
19 rclcpp::shutdown();
20 exit(signal);
21}
22
23// Client Class
24class GetCurrentInputSourceClient : public rclcpp::Node {
25public:
26 GetCurrentInputSourceClient() : Node("get_current_input_source_client") {
27
28 client_ = this->create_client<aimdk_msgs::srv::GetCurrentInputSource>(
29 "/aimdk_5Fmsgs/srv/GetCurrentInputSource");
30
31 RCLCPP_INFO(this->get_logger(),
32 "✅ GetCurrentInputSource client node created.");
33
34 // Wait for the service to become available
35 while (!client_->wait_for_service(std::chrono::seconds(2))) {
36 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
37 }
38 RCLCPP_INFO(this->get_logger(),
39 "🟢 Service available, ready to send request.");
40 }
41
42 void send_request() {
43 try {
44 auto request =
45 std::make_shared<aimdk_msgs::srv::GetCurrentInputSource::Request>();
46 request->request = aimdk_msgs::msg::CommonRequest();
47
48 RCLCPP_INFO(this->get_logger(),
49 "📨 Sending request to get current input source");
50
51 auto timeout = std::chrono::milliseconds(250);
52
53 for (int i = 0; i < 8; i++) {
54 request->request.header.stamp = this->now();
55 auto future = client_->async_send_request(request);
56 auto retcode = rclcpp::spin_until_future_complete(
57 this->shared_from_this(), future, timeout);
58 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
59 // retry as remote peer is NOT handled well by ROS
60 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
61 continue;
62 }
63 // future.done
64 auto response = future.get();
65 if (response->response.header.code == 0) {
66 RCLCPP_INFO(this->get_logger(),
67 "✅ Current input source get successfully:");
68 RCLCPP_INFO(this->get_logger(), "Name: %s",
69 response->input_source.name.c_str());
70 RCLCPP_INFO(this->get_logger(), "Priority: %d",
71 response->input_source.priority);
72 RCLCPP_INFO(this->get_logger(), "Timeout: %d",
73 response->input_source.timeout);
74 } else {
75 RCLCPP_WARN(this->get_logger(),
76 "❌ Current input source get failed, return code: %ld",
77 response->response.header.code);
78 }
79 return;
80 }
81 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
82 } catch (const std::exception &e) {
83 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
84 }
85 }
86
87private:
88 rclcpp::Client<aimdk_msgs::srv::GetCurrentInputSource>::SharedPtr client_;
89};
90
91int main(int argc, char *argv[]) {
92 try {
93 rclcpp::init(argc, argv);
94
95 signal(SIGINT, signal_handler);
96 signal(SIGTERM, signal_handler);
97
98 g_node = std::make_shared<GetCurrentInputSourceClient>();
99 auto client =
100 std::dynamic_pointer_cast<GetCurrentInputSourceClient>(g_node);
101
102 if (client) {
103 client->send_request();
104 }
105
106 g_node.reset();
107 rclcpp::shutdown();
108 return 0;
109 } catch (const std::exception &e) {
110 RCLCPP_ERROR(rclcpp::get_logger("main"),
111 "Program exited with exception: %s", e.what());
112 return 1;
113 }
114}
使用说明
# 获取当前输入源信息
ros2 run examples get_current_input_source
输出示例
[INFO] [get_current_input_source_client]: 当前输入源: node
[INFO] [get_current_input_source_client]: 优先级: 40
[INFO] [get_current_input_source_client]: 超时时间: 1000
注意事项
确保GetCurrentInputSource服务正常运行
需要在注册输入源之后才能获取到有效信息
状态码为0表示查询成功
6.2.8 机器人走跑控制
该示例中用到了mc_locomotion_velocity,以下示例通过发布/aima/mc/locomotion/velocity话题控制机器人的行走,对于v0.7之后的版本,实现速度控制前需要注册输入源(该示例已实现注册输入源),具体注册步骤可参考代码。
进入稳定站立模式后执行本程序
1#include "aimdk_msgs/msg/mc_locomotion_velocity.hpp"
2#include "aimdk_msgs/msg/common_request.hpp"
3#include "aimdk_msgs/msg/common_response.hpp"
4#include "aimdk_msgs/msg/common_state.hpp"
5#include "aimdk_msgs/msg/common_task_response.hpp"
6#include "aimdk_msgs/msg/mc_input_action.hpp"
7#include "aimdk_msgs/msg/message_header.hpp"
8#include "aimdk_msgs/srv/set_mc_input_source.hpp"
9
10#include "rclcpp/rclcpp.hpp"
11#include <chrono>
12#include <memory>
13#include <signal.h>
14#include <thread>
15
16class DirectVelocityControl : public rclcpp::Node {
17public:
18 DirectVelocityControl() : Node("direct_velocity_control") {
19 // Create publisher
20 publisher_ = this->create_publisher<aimdk_msgs::msg::McLocomotionVelocity>(
21 "/aima/mc/locomotion/velocity", 10);
22 // Create service client
23 client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
24 "/aimdk_5Fmsgs/srv/SetMcInputSource");
25
26 // Maximum speed limits
27 max_forward_speed_ = 1.0; // m/s
28 max_lateral_speed_ = 1.0; // m/s
29 max_angular_speed_ = 1.0; // rad/s
30 // Minimum speed limits (0 is also OK)
31 min_forward_speed_ = 0.2; // m/s
32 min_lateral_speed_ = 0.2; // m/s
33 min_angular_speed_ = 0.1; // rad/s
34
35 RCLCPP_INFO(this->get_logger(), "Direct velocity control node started.");
36 }
37
38 void start_publish() {
39 if (timer_ != nullptr) {
40 return;
41 }
42 // Set timer to periodically publish velocity messages (50Hz)
43 timer_ = this->create_wall_timer(
44 std::chrono::milliseconds(20),
45 std::bind(&DirectVelocityControl::publish_velocity, this));
46 }
47
48 bool register_input_source() {
49 const std::chrono::seconds timeout(8);
50 auto start_time = std::chrono::steady_clock::now();
51 while (!client_->wait_for_service(std::chrono::seconds(2))) {
52 if (std::chrono::steady_clock::now() - start_time > timeout) {
53 RCLCPP_ERROR(this->get_logger(), "Waiting for service timed out");
54 return false;
55 }
56 RCLCPP_INFO(this->get_logger(), "Waiting for input source service...");
57 }
58
59 auto request =
60 std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
61 request->action.value = 1001;
62 request->input_source.name = "node";
63 request->input_source.priority = 40;
64 request->input_source.timeout = 1000;
65
66 auto timeout2 = std::chrono::milliseconds(250);
67
68 for (int i = 0; i < 8; i++) {
69 request->request.header.stamp = this->now();
70 auto future = client_->async_send_request(request);
71 auto retcode = rclcpp::spin_until_future_complete(
72 this->shared_from_this(), future, timeout2);
73 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
74 // retry as remote peer is NOT handled well by ROS
75 RCLCPP_INFO(this->get_logger(),
76 "trying to register input source... [%d]", i);
77 continue;
78 }
79 // future.done
80 auto response = future.get();
81 int state = response->response.state.value;
82 RCLCPP_INFO(this->get_logger(),
83 "Set input source succeeded: state=%d, task_id=%lu", state,
84 response->response.task_id);
85 return true;
86 }
87 RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out");
88 return false;
89 }
90
91 void publish_velocity() {
92 auto msg = std::make_unique<aimdk_msgs::msg::McLocomotionVelocity>();
93 msg->header = aimdk_msgs::msg::MessageHeader();
94 msg->header.stamp = this->now();
95 msg->source = "node"; // Set message source
96 msg->forward_velocity = forward_velocity_;
97 msg->lateral_velocity = lateral_velocity_;
98 msg->angular_velocity = angular_velocity_;
99
100 publisher_->publish(std::move(msg));
101 RCLCPP_INFO(this->get_logger(),
102 "Published velocity: Forward %.2f m/s, Lateral %.2f m/s, "
103 "Angular %.2f rad/s",
104 forward_velocity_, lateral_velocity_, angular_velocity_);
105 }
106
107 void clear_velocity() {
108 forward_velocity_ = 0.0;
109 lateral_velocity_ = 0.0;
110 angular_velocity_ = 0.0;
111 }
112
113 bool set_forward(double forward) {
114 if (abs(forward) < 0.005) {
115 forward_velocity_ = 0.0;
116 return true;
117 } else if ((abs(forward) > max_forward_speed_) ||
118 (abs(forward) < min_forward_speed_)) {
119 RCLCPP_ERROR(this->get_logger(), "input value out of range, exiting");
120 return false;
121 } else {
122 forward_velocity_ = forward;
123 return true;
124 }
125 }
126
127 bool set_lateral(double lateral) {
128 if (abs(lateral) < 0.005) {
129 lateral_velocity_ = 0.0;
130 return true;
131 } else if ((abs(lateral) > max_lateral_speed_) ||
132 (abs(lateral) < min_lateral_speed_)) {
133 RCLCPP_ERROR(this->get_logger(), "input value out of range, exiting");
134 return false;
135 } else {
136 lateral_velocity_ = lateral;
137 return true;
138 }
139 }
140
141 bool set_angular(double angular) {
142 if (abs(angular) < 0.005) {
143 angular_velocity_ = 0.0;
144 return true;
145 } else if ((abs(angular) > max_angular_speed_) ||
146 (abs(angular) < min_angular_speed_)) {
147 RCLCPP_ERROR(this->get_logger(), "input value out of range, exiting");
148 return false;
149 } else {
150 angular_velocity_ = angular;
151 return true;
152 }
153 }
154
155private:
156 rclcpp::Publisher<aimdk_msgs::msg::McLocomotionVelocity>::SharedPtr
157 publisher_;
158 rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr client_;
159 rclcpp::TimerBase::SharedPtr timer_;
160
161 double forward_velocity_;
162 double lateral_velocity_;
163 double angular_velocity_;
164
165 double max_forward_speed_;
166 double max_lateral_speed_;
167 double max_angular_speed_;
168
169 double min_forward_speed_;
170 double min_lateral_speed_;
171 double min_angular_speed_;
172};
173
174// Signal Processing
175std::shared_ptr<DirectVelocityControl> global_node = nullptr;
176void signal_handler(int sig) {
177 if (global_node) {
178 global_node->clear_velocity();
179 RCLCPP_INFO(global_node->get_logger(),
180 "Received signal %d: clearing velocity and shutting down", sig);
181 }
182 rclcpp::shutdown();
183 exit(sig);
184}
185
186int main(int argc, char *argv[]) {
187 rclcpp::init(argc, argv);
188 signal(SIGINT, signal_handler);
189 signal(SIGTERM, signal_handler);
190
191 global_node = std::make_shared<DirectVelocityControl>();
192 auto node = global_node;
193
194 if (!node->register_input_source()) {
195 RCLCPP_ERROR(node->get_logger(),
196 "Input source registration failed, exiting");
197 global_node.reset();
198 rclcpp::shutdown();
199 return 1;
200 }
201
202 // get and check control values
203 // notice that mc has thresholds to start movement
204 double forward, lateral, angular;
205 std::cout << "Enter forward speed 0 or ±(0.2 ~ 1.0) m/s: ";
206 std::cin >> forward;
207 if (!node->set_forward(forward)) {
208 return 2;
209 }
210 std::cout << "Enter lateral speed 0 or ±(0.2 ~ 1.0) m/s: ";
211 std::cin >> lateral;
212 if (!node->set_lateral(lateral)) {
213 return 2;
214 }
215 std::cout << "Enter angular speed 0 or ±(0.1 ~ 1.0) rad/s: ";
216 std::cin >> angular;
217 if (!node->set_angular(angular)) {
218 return 2;
219 }
220
221 RCLCPP_INFO(node->get_logger(), "Setting velocity; moving for 5 seconds");
222
223 node->start_publish();
224
225 auto start_time = node->now();
226 while ((node->now() - start_time).seconds() < 5.0) {
227 rclcpp::spin_some(node);
228 std::this_thread::sleep_for(std::chrono::milliseconds(1));
229 }
230
231 node->clear_velocity();
232 RCLCPP_INFO(node->get_logger(), "5 seconds elapsed; robot stopped");
233
234 rclcpp::spin(node);
235 rclcpp::shutdown();
236 return 0;
237}
6.2.9 关节电机控制
本示例展示了如何使用ROS2和Ruckig库来控制机器人的关节运动。
注意
注意⚠️ :运行本示例前需要在机器人运控计算单元(PC1)使用aima em stop-app mc关闭原生运控模块,获取机器人控制权。注意确保机器人安全
!该示例代码直接对底层电机也就是HAL层进行控制,在运行程序前请检查代码中对关节的安全限位是否与所用机器人相符, 并确保安全!
机器人关节控制示例
这个示例展示了如何使用ROS2和Ruckig库来控制机器人的关节运动。示例实现了以下功能:
机器人关节模型定义
基于Ruckig的轨迹插补
多关节协同控制
实时位置、速度和加速度控制
依赖项
ROS2
Ruckig库
aimdk_msgs包
构建说明
将代码放入ROS2工作空间的src目录
在CMakeLists.txt中添加:
find_package(rclcpp REQUIRED)
find_package(aimdk_msgs REQUIRED)
find_package(ruckig REQUIRED)
add_executable(joint_control_example joint_control_example.cpp)
ament_target_dependencies(joint_control_example
rclcpp
aimdk_msgs
ruckig
)
在package.xml中添加依赖:
<depend>rclcpp</depend>
<depend>aimdk_msgs</depend>
<depend>ruckig</depend>
示例功能说明
创建了四个控制器节点,分别控制:
腿部x2(12个关节)
腰部x1(3个关节)
手臂x2(14个关节)
头部x1(2个关节)
演示功能:
每10秒让指定关节在正负0.5弧度之间摆动
使用Ruckig库实现平滑的运动轨迹
实时发布关节控制命令
自定义使用
添加新的控制逻辑:
修改
SetTargetPosition函数添加新的控制回调函数
调整控制频率:
修改
control_timer_的周期(当前为2ms)
1#include <aimdk_msgs/msg/joint_command_array.hpp>
2#include <aimdk_msgs/msg/joint_state_array.hpp>
3#include <atomic>
4#include <cstdlib>
5#include <memory>
6#include <rclcpp/rclcpp.hpp>
7#include <ruckig/ruckig.hpp>
8#include <signal.h>
9#include <string>
10#include <unordered_map>
11#include <vector>
12
13/**
14 * @brief Global variables and signal handling
15 */
16// Global variables to control program state
17std::atomic<bool> g_running(true);
18std::atomic<bool> g_emergency_stop(false);
19
20// Signal handler function
21void signal_handler(int) {
22 g_running = false;
23 RCLCPP_INFO(rclcpp::get_logger("main"),
24 "Received termination signal, shutting down...");
25}
26
27/**
28 * @brief Robot model definition
29 */
30enum class JointArea {
31 HEAD, // Head joints
32 ARM, // Arm joints
33 WAIST, // Waist joints
34 LEG, // Leg joints
35};
36
37/**
38 * @brief Joint information structure
39 */
40struct JointInfo {
41 std::string name; // Joint name
42 double lower_limit; // Joint angle lower limit
43 double upper_limit; // Joint angle upper limit
44 double kp; // Position control gain
45 double kd; // Velocity control gain
46};
47
48/**
49 * @brief Robot model configuration
50 * Contains parameters for all joints, enabling or disabling specific joints as
51 * needed
52 */
53std::map<JointArea, std::vector<JointInfo>> robot_model = {
54 {JointArea::LEG,
55 {
56 // Left leg joint configuration
57 {"left_hip_pitch_joint", -2.704, 2.556, 40.0, 4.0},
58 {"left_hip_roll_joint", -0.235, 2.906, 40.0, 4.0},
59 {"left_hip_yaw_joint", -1.684, 3.430, 30.0, 3.0},
60 {"left_knee_joint", 0.0000, 2.4073, 80.0, 8.0},
61 {"left_ankle_pitch_joint", -0.803, 0.453, 40.0, 4.0},
62 {"left_ankle_roll_joint", -0.2625, 0.2625, 20.0, 2.0},
63 // Right leg joint configuration
64 {"right_hip_pitch_joint", -2.704, 2.556, 40.0, 4.0},
65 {"right_hip_roll_joint", -2.906, 0.235, 40.0, 4.0},
66 {"right_hip_yaw_joint", -3.430, 1.684, 30.0, 3.0},
67 {"right_knee_joint", 0.0000, 2.4073, 80.0, 8.0},
68 {"right_ankle_pitch_joint", -0.803, 0.453, 40.0, 4.0},
69 {"right_ankle_roll_joint", -0.2625, 0.2625, 20.0, 2.0},
70 }},
71
72 {JointArea::WAIST,
73 {
74 // Waist joint configuration
75 {"waist_yaw_joint", -3.43, 2.382, 20.0, 4.0},
76 {"waist_pitch_joint", -0.314, 0.314, 20.0, 4.0},
77 {"waist_roll_joint", -0.488, 0.488, 20.0, 4.0},
78 }},
79 {JointArea::ARM,
80 {
81 // Left arm joint configuration
82 {"left_shoulder_pitch_joint", -3.08, 2.04, 20.0, 2.0},
83 {"left_shoulder_roll_joint", -0.061, 2.993, 20.0, 2.0},
84 {"left_shoulder_yaw_joint", -2.556, 2.556, 20.0, 2.0},
85 {"left_elbow_joint", -2.3556, 0.0, 20.0, 2.0},
86 {"left_wrist_yaw_joint", -2.556, 2.556, 20.0, 2.0},
87 {"left_wrist_pitch_joint", -0.558, 0.558, 20.0, 2.0},
88 {"left_wrist_roll_joint", -1.571, 0.724, 20.0, 2.0},
89 // Right arm joint configuration
90 {"right_shoulder_pitch_joint", -3.08, 2.04, 20.0, 2.0},
91 {"right_shoulder_roll_joint", -2.993, 0.061, 20.0, 2.0},
92 {"right_shoulder_yaw_joint", -2.556, 2.556, 20.0, 2.0},
93 {"right_elbow_joint", -2.3556, 0.0000, 20.0, 2.0},
94 {"right_wrist_yaw_joint", -2.556, 2.556, 20.0, 2.0},
95 {"right_wrist_pitch_joint", -0.558, 0.558, 20.0, 2.0},
96 {"right_wrist_roll_joint", -0.724, 1.571, 20.0, 2.0},
97 }},
98 {JointArea::HEAD,
99 {
100 // Head joint configuration
101 {"head_yaw_joint", -0.366, 0.366, 20.0, 2.0},
102 {"head_pitch_joint", -0.3838, 0.3838, 20.0, 2.0},
103 }},
104};
105
106/**
107 * @brief Joint controller node class
108 * @tparam DOFs Degrees of freedom
109 * @tparam Area Joint area
110 */
111template <int DOFs, JointArea Area>
112class JointControllerNode : public rclcpp::Node {
113public:
114 /**
115 * @brief Constructor
116 * @param node_name Node name
117 * @param sub_topic Subscription topic name
118 * @param pub_topic Publication topic name
119 * @param qos QoS configuration
120 */
121 JointControllerNode(std::string node_name, std::string sub_topic,
122 std::string pub_topic,
123 rclcpp::QoS qos = rclcpp::SensorDataQoS())
124 : Node(node_name), ruckig(0.002) {
125 joint_info_ = robot_model[Area];
126 if (joint_info_.size() != DOFs) {
127 RCLCPP_ERROR(this->get_logger(), "Joint count mismatch.");
128 exit(1);
129 }
130
131 // Set motion constraints for Ruckig trajectory planner
132 for (int i = 0; i < DOFs; ++i) {
133 input.max_velocity[i] = 1.0; // Max velocity limit
134 input.max_acceleration[i] = 1.0; // Max acceleration limit
135 input.max_jerk[i] = 25.0; // Max jerk (change of acceleration) limit
136 }
137
138 // Create joint state subscriber
139 sub_ = this->create_subscription<aimdk_msgs::msg::JointStateArray>(
140 sub_topic, qos,
141 std::bind(&JointControllerNode::JointStateCallback, this,
142 std::placeholders::_1));
143
144 // Create joint command publisher
145 pub_ = this->create_publisher<aimdk_msgs::msg::JointCommandArray>(pub_topic,
146 qos);
147 }
148
149private:
150 // Ruckig trajectory planner variables
151 ruckig::Ruckig<DOFs> ruckig; // Trajectory planner instance
152 ruckig::InputParameter<DOFs> input; // Input parameters
153 ruckig::OutputParameter<DOFs> output; // Output parameters
154 bool ruckig_initialized_ = false; // Trajectory planner initialization flag
155 std::vector<JointInfo> joint_info_; // Joint information list
156
157 // ROS communication variables
158 rclcpp::Subscription<aimdk_msgs::msg::JointStateArray>::SharedPtr
159 sub_; // State subscriber
160 rclcpp::Publisher<aimdk_msgs::msg::JointCommandArray>::SharedPtr
161 pub_; // Command publisher
162
163 /**
164 * @brief Joint state callback function
165 * @param msg Joint state message
166 */
167 void
168 JointStateCallback(const aimdk_msgs::msg::JointStateArray::SharedPtr msg) {
169 // Initialize trajectory planner on first state reception
170 if (!ruckig_initialized_) {
171 for (int i = 0; i < DOFs; ++i) {
172 input.current_position[i] = msg->joints[i].position;
173 input.current_velocity[i] = msg->joints[i].velocity;
174 input.current_acceleration[i] = 0.0;
175 }
176 ruckig_initialized_ = true;
177 RCLCPP_INFO(this->get_logger(),
178 "Ruckig trajectory planner initialization complete");
179 }
180 }
181
182public:
183 /**
184 * @brief Set target joint position
185 * @param joint_name Joint name
186 * @param target_position Target position
187 * @return Whether the target position was successfully set
188 */
189 bool SetTargetPosition(std::string joint_name, double target_position) {
190 if (!ruckig_initialized_) {
191 RCLCPP_WARN(this->get_logger(),
192 "Ruckig trajectory planner not initialized");
193 return false;
194 }
195
196 // Find target joint and set its position
197 int target_joint = -1;
198 for (int i = 0; i < DOFs; ++i) {
199 if (joint_info_[i].name == joint_name) {
200 // Check if target position is within limits
201 if (target_position < joint_info_[i].lower_limit ||
202 target_position > joint_info_[i].upper_limit) {
203 RCLCPP_ERROR(
204 this->get_logger(),
205 "Target position %.3f exceeds limit for joint %s [%.3f, %.3f]",
206 target_position, joint_name.c_str(), joint_info_[i].lower_limit,
207 joint_info_[i].upper_limit);
208 return false;
209 }
210 input.target_position[i] = target_position;
211 input.target_velocity[i] = 0.0;
212 input.target_acceleration[i] = 0.0;
213 target_joint = i;
214 } else {
215 input.target_position[i] = input.current_position[i];
216 input.target_velocity[i] = 0.0;
217 input.target_acceleration[i] = 0.0;
218 }
219 }
220
221 if (target_joint == -1) {
222 RCLCPP_ERROR(this->get_logger(), "Joint %s not found",
223 joint_name.c_str());
224 return false;
225 }
226
227 // Perform trajectory planning and send command using Ruckig
228 const double tolerance = 1e-6;
229 while (g_running && rclcpp::ok() && !g_emergency_stop) {
230 auto result = ruckig.update(input, output);
231 if (result != ruckig::Result::Working &&
232 result != ruckig::Result::Finished) {
233 RCLCPP_WARN(this->get_logger(), "Trajectory planning failed");
234 break;
235 }
236
237 // Update current state
238 for (int i = 0; i < DOFs; ++i) {
239 input.current_position[i] = output.new_position[i];
240 input.current_velocity[i] = output.new_velocity[i];
241 input.current_acceleration[i] = output.new_acceleration[i];
242 }
243
244 // Check if target position is reached
245 if (std::abs(output.new_position[target_joint] - target_position) <
246 tolerance) {
247 RCLCPP_INFO(this->get_logger(), "Joint %s reached target position",
248 joint_name.c_str());
249 break;
250 }
251
252 // Create and send joint command
253 aimdk_msgs::msg::JointCommandArray cmd;
254 cmd.joints.resize(DOFs);
255 for (int i = 0; i < DOFs; ++i) {
256 auto &joint = joint_info_[i];
257 cmd.joints[i].name = joint.name;
258 cmd.joints[i].position = output.new_position[i];
259 cmd.joints[i].velocity = output.new_velocity[i];
260 cmd.joints[i].stiffness = joint.kp;
261 cmd.joints[i].damping = joint.kd;
262 }
263 pub_->publish(cmd);
264
265 // Short delay to avoid excessive CPU usage
266 std::this_thread::sleep_for(std::chrono::milliseconds(2));
267 }
268
269 return true;
270 }
271
272 /**
273 * @brief Safely stop all joints
274 */
275 void safe_stop() {
276 if (!ruckig_initialized_) {
277 RCLCPP_WARN(this->get_logger(), "Ruckig trajectory planner not "
278 "initialized, cannot perform safe stop");
279 return;
280 }
281
282 RCLCPP_INFO(this->get_logger(), "Performing safe stop...");
283
284 // Set all joint target positions to current positions
285 for (int i = 0; i < DOFs; ++i) {
286 input.target_position[i] = input.current_position[i];
287 input.target_velocity[i] = 0.0;
288 input.target_acceleration[i] = 0.0;
289 }
290
291 // Send final command to ensure joints stop
292 aimdk_msgs::msg::JointCommandArray cmd;
293 cmd.joints.resize(DOFs);
294 for (int i = 0; i < DOFs; ++i) {
295 auto &joint = joint_info_[i];
296 cmd.joints[i].name = joint.name;
297 cmd.joints[i].position = input.current_position[i];
298 cmd.joints[i].velocity = 0.0;
299 cmd.joints[i].stiffness = joint.kp;
300 cmd.joints[i].damping = joint.kd;
301 }
302 pub_->publish(cmd);
303
304 RCLCPP_INFO(this->get_logger(), "Safe stop complete");
305 }
306
307 /**
308 * @brief Emergency stop for all joints
309 */
310 void emergency_stop() {
311 g_emergency_stop = true;
312 safe_stop();
313 RCLCPP_ERROR(this->get_logger(), "Emergency stop triggered");
314 }
315};
316
317/**
318 * @brief Main function
319 */
320int main(int argc, char *argv[]) {
321 rclcpp::init(argc, argv);
322
323 // Set up signal handling
324 signal(SIGINT, signal_handler);
325 signal(SIGTERM, signal_handler);
326
327 try {
328 // Create leg controller node
329 auto leg_node = std::make_shared<JointControllerNode<12, JointArea::LEG>>(
330 "leg_node", "/aima/hal/joint/leg/state", "/aima/hal/joint/leg/command");
331
332 // Create timer node
333 rclcpp::Node::SharedPtr timer_node =
334 rclcpp::Node::make_shared("timer_node");
335 double position = 0.8;
336
337 // Create timer callback function
338 auto timer = timer_node->create_wall_timer(std::chrono::seconds(3), [&]() {
339 if (!g_running || g_emergency_stop)
340 return; // If the program is shutting down or emergency stopped, do not
341 // execute new actions
342 position = -position;
343 position = 1.3 + position;
344 if (!leg_node->SetTargetPosition("left_knee_joint", position)) {
345 RCLCPP_ERROR(rclcpp::get_logger("main"),
346 "Failed to set target position");
347 }
348 });
349
350 // Create executor
351 rclcpp::executors::MultiThreadedExecutor executor;
352 executor.add_node(leg_node);
353 executor.add_node(timer_node);
354
355 // Main loop
356 while (g_running && rclcpp::ok() && !g_emergency_stop) {
357 executor.spin_once(std::chrono::milliseconds(100));
358 }
359
360 // Safely stop all joints
361 RCLCPP_INFO(rclcpp::get_logger("main"), "Safely stopping all joints...");
362 leg_node->safe_stop();
363
364 // Wait a short time to ensure command transmission is complete
365 std::this_thread::sleep_for(std::chrono::milliseconds(100));
366
367 // Clean up resources
368 RCLCPP_INFO(rclcpp::get_logger("main"), "Cleaning up resources...");
369 leg_node.reset();
370 timer_node.reset();
371
372 } catch (const std::exception &e) {
373 RCLCPP_ERROR(rclcpp::get_logger("main"), "Exception occurred: %s",
374 e.what());
375 g_emergency_stop = true;
376 } catch (...) {
377 RCLCPP_ERROR(rclcpp::get_logger("main"), "Unknown exception occurred");
378 g_emergency_stop = true;
379 }
380
381 RCLCPP_INFO(rclcpp::get_logger("main"), "Program exited safely");
382 rclcpp::shutdown();
383 return 0;
384}
6.2.10 键盘控制机器人
本示例实现了通过PC的键盘输入控制机器人的前进后退转弯的功能。
通过WASD控制机器人行走方向,增加/减少速度(±0.2 m/s),Q/E增加/减少角速度(±0.1 rad/s),ESC退出程序并释放终端资源,Space立即将速度归零,执行急停。
小心
注意:运行本示例前需要先使用手柄,将机器人切入稳定站立模式。(位控站立/走跑模式时按R2+X, 其他模式详见模式流转图进行操作),然后在机器人的终端界面使用:aima em stop-app rc关闭遥控器,防止通道占用。
实现键盘控制前需要注册输入源(该示例已实现注册输入源)
运行前需要安装curse模块:
sudo apt install libncurses-dev
1#include "aimdk_msgs/msg/common_request.hpp"
2#include "aimdk_msgs/msg/common_response.hpp"
3#include "aimdk_msgs/msg/common_state.hpp"
4#include "aimdk_msgs/msg/common_task_response.hpp"
5#include "aimdk_msgs/msg/mc_input_action.hpp"
6#include "aimdk_msgs/msg/mc_locomotion_velocity.hpp"
7#include "aimdk_msgs/msg/message_header.hpp"
8#include "aimdk_msgs/srv/set_mc_input_source.hpp"
9
10#include <algorithm>
11#include <chrono>
12#include <csignal>
13#include <curses.h>
14#include <rclcpp/rclcpp.hpp>
15
16using aimdk_msgs::msg::McLocomotionVelocity;
17using std::placeholders::_1;
18
19class KeyboardVelocityController : public rclcpp::Node {
20public:
21 KeyboardVelocityController()
22 : Node("keyboard_velocity_controller"), forward_velocity_(0.0),
23 lateral_velocity_(0.0), angular_velocity_(0.0), step_(0.2),
24 angular_step_(0.1) {
25 pub_ = this->create_publisher<McLocomotionVelocity>(
26 "/aima/mc/locomotion/velocity", 10);
27 client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
28 "/aimdk_5Fmsgs/srv/SetMcInputSource");
29 // Register input source
30 if (!register_input_source()) {
31 RCLCPP_ERROR(this->get_logger(),
32 "Input source registration failed, exiting");
33 throw std::runtime_error("Input source registration failed");
34 }
35 // Initialize ncurses
36 initscr();
37 cbreak();
38 noecho();
39 keypad(stdscr, TRUE);
40 nodelay(stdscr, TRUE);
41
42 timer_ = this->create_wall_timer(
43 std::chrono::milliseconds(50),
44 std::bind(&KeyboardVelocityController::checkKeyAndPublish, this));
45
46 RCLCPP_INFO(this->get_logger(),
47 "Control started: W/S Forward/Backward | A/D Strafe Left/Right "
48 "| Q/E Turn Left/Right | Space Stop | ESC Exit");
49 }
50
51 ~KeyboardVelocityController() {
52 endwin(); // Restore terminal
53 }
54
55private:
56 rclcpp::Publisher<McLocomotionVelocity>::SharedPtr pub_;
57 rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr client_;
58 rclcpp::TimerBase::SharedPtr timer_;
59
60 float forward_velocity_, lateral_velocity_, angular_velocity_;
61 const float step_, angular_step_;
62
63 bool register_input_source() {
64 const std::chrono::seconds srv_timeout(8);
65 auto start_time = std::chrono::steady_clock::now();
66 while (!client_->wait_for_service(std::chrono::seconds(2))) {
67 if (std::chrono::steady_clock::now() - start_time > srv_timeout) {
68 RCLCPP_ERROR(this->get_logger(), "Waiting for service timed out");
69 return false;
70 }
71 RCLCPP_INFO(this->get_logger(), "Waiting for input source service...");
72 }
73
74 auto request =
75 std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
76 request->action.value = 1001;
77 request->input_source.name = "node";
78 request->input_source.priority = 40;
79 request->input_source.timeout = 1000;
80
81 auto timeout = std::chrono::milliseconds(250);
82
83 for (int i = 0; i < 8; i++) {
84 request->request.header.stamp = this->now();
85 auto future = client_->async_send_request(request);
86 auto retcode = rclcpp::spin_until_future_complete(
87 this->get_node_base_interface(), future, timeout);
88 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
89 // retry as remote peer is NOT handled well by ROS
90 RCLCPP_INFO(this->get_logger(),
91 "trying to register input source... [%d]", i);
92 continue;
93 }
94 // future.done
95 auto response = future.get();
96 int state = response->response.state.value;
97 RCLCPP_INFO(this->get_logger(),
98 "Set input source succeeded: state=%d, task_id=%lu", state,
99 response->response.task_id);
100 return true;
101 }
102 RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out");
103 return false;
104 }
105
106 void checkKeyAndPublish() {
107 int ch = getch(); // non-blocking read
108
109 switch (ch) {
110 case ' ': // Space key
111 forward_velocity_ = 0.0;
112 lateral_velocity_ = 0.0;
113 angular_velocity_ = 0.0;
114 break;
115 case 'w':
116 forward_velocity_ = std::min(forward_velocity_ + step_, 1.0f);
117 break;
118 case 's':
119 forward_velocity_ = std::max(forward_velocity_ - step_, -1.0f);
120 break;
121 case 'a':
122 lateral_velocity_ = std::min(lateral_velocity_ + step_, 1.0f);
123 break;
124 case 'd':
125 lateral_velocity_ = std::max(lateral_velocity_ - step_, -1.0f);
126 break;
127 case 'q':
128 angular_velocity_ = std::min(angular_velocity_ + angular_step_, 1.0f);
129 break;
130 case 'e':
131 angular_velocity_ = std::max(angular_velocity_ - angular_step_, -1.0f);
132 break;
133 case 27: // ESC Key
134 RCLCPP_INFO(this->get_logger(), "Exiting control");
135 rclcpp::shutdown();
136 return;
137 }
138
139 auto msg = std::make_unique<McLocomotionVelocity>();
140 msg->header = aimdk_msgs::msg::MessageHeader();
141 msg->header.stamp = this->now();
142 msg->source = "node";
143 msg->forward_velocity = forward_velocity_;
144 msg->lateral_velocity = lateral_velocity_;
145 msg->angular_velocity = angular_velocity_;
146
147 float fwd = forward_velocity_;
148 float lat = lateral_velocity_;
149 float ang = angular_velocity_;
150
151 pub_->publish(std::move(msg));
152
153 // Screen Output
154 clear();
155 mvprintw(0, 0,
156 "W/S: Forward/Backward | A/D: Left/Right Strafe | Q/E: Turn "
157 "Left/Right | Space: Stop | ESC: Exit");
158 mvprintw(2, 0,
159 "Speed Status: Forward: %.2f m/s | Lateral: %.2f m/s | Angular: "
160 "%.2f rad/s",
161 fwd, lat, ang);
162 refresh();
163 }
164};
165
166int main(int argc, char *argv[]) {
167 rclcpp::init(argc, argv);
168 try {
169 auto node = std::make_shared<KeyboardVelocityController>();
170 rclcpp::spin(node);
171 } catch (const std::exception &e) {
172 RCLCPP_FATAL(rclcpp::get_logger("main"),
173 "Program exited with exception: %s", e.what());
174 }
175 rclcpp::shutdown();
176 return 0;
177}
6.2.11 拍照
该示例中用到了take_photo,在运行节点程序前,修改需要拍照的相机话题,启动节点程序后,会创建/images/目录,将当前帧图像保存在这个目录下。
1#include <chrono>
2#include <cv_bridge/cv_bridge.h>
3#include <filesystem>
4#include <opencv2/opencv.hpp>
5#include <rclcpp/rclcpp.hpp>
6#include <sensor_msgs/msg/image.hpp>
7#include <string>
8
9class SaveOneRaw : public rclcpp::Node {
10public:
11 SaveOneRaw() : Node("save_one_image"), saved_(false) {
12 topic_ = declare_parameter<std::string>(
13 "image_topic", "/aima/hal/sensor/stereo_head_front_left/rgb_image");
14
15 std::filesystem::create_directories("images");
16
17 auto qos = rclcpp::SensorDataQoS(); // BestEffort/Volatile
18 sub_ = create_subscription<sensor_msgs::msg::Image>(
19 topic_, qos, std::bind(&SaveOneRaw::cb, this, std::placeholders::_1));
20
21 RCLCPP_INFO(get_logger(), "Subscribing (raw): %s", topic_.c_str());
22 }
23
24private:
25 void cb(const sensor_msgs::msg::Image::SharedPtr msg) {
26 if (saved_)
27 return;
28
29 try {
30 // Obtain the Mat without copying by not specifying encoding
31 cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
32 cv::Mat img = cvp->image;
33
34 // Convert to BGR for uniform saving
35 if (msg->encoding == "rgb8") {
36 cv::cvtColor(img, img, cv::COLOR_RGB2BGR);
37 } else if (msg->encoding == "mono8") {
38 cv::cvtColor(img, img, cv::COLOR_GRAY2BGR);
39 } // bgr8 Use this directly; add more branches as needed to support
40 // additional encodings.
41
42 auto now = std::chrono::system_clock::now();
43 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
44 now.time_since_epoch())
45 .count();
46 std::string path = "images/frame_" + std::to_string(ms) + ".png";
47
48 if (cv::imwrite(path, img)) {
49 RCLCPP_INFO(get_logger(), "Saved: %s (%dx%d)", path.c_str(), img.cols,
50 img.rows);
51 saved_ = true;
52 rclcpp::shutdown();
53 } else {
54 RCLCPP_ERROR(get_logger(), "cv::imwrite failed: %s", path.c_str());
55 }
56 } catch (const std::exception &e) {
57 RCLCPP_ERROR(get_logger(), "raw decode failed: %s", e.what());
58 // Do not set the saved flag; wait for the next frame
59 }
60 }
61
62 std::string topic_;
63 bool saved_;
64 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_;
65};
66
67int main(int argc, char **argv) {
68 rclcpp::init(argc, argv);
69 rclcpp::spin(std::make_shared<SaveOneRaw>());
70 return 0;
71}
6.2.12 相机推流示例集
该示例集提供了多种相机数据订阅和处理功能,支持深度相机、双目相机和单目相机的数据流订阅。
这些相机数据订阅 example 并没有实际的业务用途, 仅提供相机数据基础信息的打印;若您比较熟悉 ros2 使用,会发现 ros2 topic echo + ros2 topic hz 也能够实现 example 提供的功能。
您可以选择快速查阅 SDK 接口手册中话题列表直接快速进入自己模块的开发,也可以使用相机 example 作为脚手架加入自己的业务逻辑。
我们发布的传感器数据均为未经预处理(去畸变等)的原始数据,如果您需要查询传感器的详细信息(如分辨率、焦距等),请关注相应的内参(camera_info)话题。
深度相机数据订阅
该示例中用到了echo_camera_rgbd,通过订阅/aima/hal/sensor/rgbd_head_front/话题来接收机器人的深度相机数据,支持深度点云、深度图、RGB图、压缩RGB图和相机内参等多种数据类型。
功能特点:
支持多种数据类型订阅(深度点云、深度图、RGB图、压缩图、相机内参)
实时FPS统计和数据显示
支持RGB图视频录制功能
可配置的topic类型选择
支持的数据类型:
depth_pointcloud: 深度点云数据 (sensor_msgs/PointCloud2)depth_image: 深度图像 (sensor_msgs/Image)rgb_image: RGB图像 (sensor_msgs/Image)rgb_image_compressed: 压缩RGB图像 (sensor_msgs/CompressedImage)camera_info: 相机内参 (sensor_msgs/CameraInfo)
1#include <deque>
2#include <iomanip>
3#include <memory>
4#include <rclcpp/rclcpp.hpp>
5#include <sensor_msgs/msg/camera_info.hpp>
6#include <sensor_msgs/msg/compressed_image.hpp>
7#include <sensor_msgs/msg/image.hpp>
8#include <sensor_msgs/msg/point_cloud2.hpp>
9#include <sstream>
10#include <string>
11#include <vector>
12
13// OpenCV headers for image/video writing
14#include <cv_bridge/cv_bridge.h>
15#include <opencv2/opencv.hpp>
16
17/**
18 * @brief Example of subscribing to multiple topics for the head depth camera
19 *
20 * You can select which topic type to subscribe to via the startup argument
21 * --ros-args -p topic_type:=<type>:
22 * - depth_pointcloud: Depth point cloud (sensor_msgs/PointCloud2)
23 * - depth_image: Depth image (sensor_msgs/Image)
24 * - rgb_image: RGB image (sensor_msgs/Image)
25 * - rgb_image_compressed: RGB compressed image (sensor_msgs/CompressedImage)
26 * - rgb_camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
27 * - depth_camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
28 *
29 * Examples:
30 * ros2 run examples echo_camera_rgbd --ros-args -p
31 * topic_type:=depth_pointcloud ros2 run examples echo_camera_rgbd --ros-args -p
32 * topic_type:=rgb_image ros2 run examples echo_camera_rgbd --ros-args -p
33 * topic_type:=rgb_camera_info
34 *
35 * topic_type defaults to "rgb_image"
36 *
37 * See individual callbacks for more detailed comments
38 */
39class CameraTopicEcho : public rclcpp::Node {
40public:
41 CameraTopicEcho() : Node("camera_topic_echo") {
42 // Select which topic type to subscribe to
43 topic_type_ = declare_parameter<std::string>("topic_type", "rgb_image");
44 dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
45
46 // Subscribed topics and their message layouts
47 // 1. /aima/hal/sensor/rgbd_head_front/depth_pointcloud
48 // - topic_type: depth_pointcloud
49 // - message type: sensor_msgs::msg::PointCloud2
50 // - frame_id: rgbd_head_front
51 // - child_frame_id: /
52 // - contents: depth point cloud
53 // 2. /aima/hal/sensor/rgbd_head_front/depth_image
54 // - topic_type: depth_image
55 // - message type: sensor_msgs::msg::Image
56 // - frame_id: rgbd_head_front
57 // - contents: depth image
58 // 3. /aima/hal/sensor/rgbd_head_front/rgb_image
59 // - topic_type: rgb_image
60 // - message type: sensor_msgs::msg::Image
61 // - frame_id: rgbd_head_front
62 // - contents: RGB image
63 // 4. /aima/hal/sensor/rgbd_head_front/rgb_image/compressed
64 // - topic_type: rgb_image_compressed
65 // - message type: sensor_msgs::msg::CompressedImage
66 // - frame_id: rgbd_head_front
67 // - contents: RGB compressed image
68 // 5. /aima/hal/sensor/rgbd_head_front/rgb_camera_info
69 // - topic_type: camera_info
70 // - message type: sensor_msgs::msg::CameraInfo
71 // - frame_id: rgbd_head_front
72 // - contents: RGB camera intrinsic parameters
73 // 6. /aima/hal/sensor/rgbd_head_front/depth_camera_info
74 // - topic_type: camera_info
75 // - message type: sensor_msgs::msg::CameraInfo
76 // - frame_id: rgbd_head_front
77 // - contents: RGB camera intrinsic parameters
78
79 auto qos = rclcpp::SensorDataQoS();
80
81 // Enable depth pointcloud subscription
82 if (topic_type_ == "depth_pointcloud") {
83 topic_name_ = "/aima/hal/sensor/rgbd_head_front/depth_pointcloud";
84 sub_pointcloud_ = create_subscription<sensor_msgs::msg::PointCloud2>(
85 topic_name_, qos,
86 std::bind(&CameraTopicEcho::cb_pointcloud, this,
87 std::placeholders::_1));
88 RCLCPP_INFO(get_logger(), "✅ Subscribing PointCloud2: %s",
89 topic_name_.c_str());
90
91 // Enable depth image subscription
92 } else if (topic_type_ == "depth_image") {
93 topic_name_ = "/aima/hal/sensor/rgbd_head_front/depth_image";
94 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
95 topic_name_, qos,
96 std::bind(&CameraTopicEcho::cb_image, this, std::placeholders::_1));
97 RCLCPP_INFO(get_logger(), "✅ Subscribing Depth Image: %s",
98 topic_name_.c_str());
99
100 // Enable RGB image subscription
101 } else if (topic_type_ == "rgb_image") {
102 topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_image";
103 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
104 topic_name_, qos,
105 std::bind(&CameraTopicEcho::cb_image, this, std::placeholders::_1));
106 RCLCPP_INFO(get_logger(), "✅ Subscribing RGB Image: %s",
107 topic_name_.c_str());
108 if (!dump_video_path_.empty()) {
109 RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
110 dump_video_path_.c_str());
111 }
112
113 // Enable RGB compressed image subscription
114 } else if (topic_type_ == "rgb_image_compressed") {
115 topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_image/compressed";
116 sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
117 topic_name_, qos,
118 std::bind(&CameraTopicEcho::cb_compressed, this,
119 std::placeholders::_1));
120 RCLCPP_INFO(get_logger(), "✅ Subscribing CompressedImage: %s",
121 topic_name_.c_str());
122
123 // Enable rgb camera info subscription
124 } else if (topic_type_ == "rgb_camera_info") {
125 topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_camera_info";
126 // RGB-D CameraInfo subscriptions is different with other cameras.
127 // The messages arrive in about 10Hz and SensorDataQoS is enough.
128 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
129 topic_name_, qos,
130 std::bind(&CameraTopicEcho::cb_camerainfo, this,
131 std::placeholders::_1));
132 RCLCPP_INFO(get_logger(), "✅ Subscribing RGB CameraInfo: %s",
133 topic_name_.c_str());
134
135 // Enable depth camera info subscription
136 } else if (topic_type_ == "depth_camera_info") {
137 topic_name_ = "/aima/hal/sensor/rgbd_head_front/depth_camera_info";
138 // RGB-D CameraInfo subscriptions is different with other cameras.
139 // The messages arrive in about 10Hz and SensorDataQoS is enough.
140 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
141 topic_name_, qos,
142 std::bind(&CameraTopicEcho::cb_camerainfo, this,
143 std::placeholders::_1));
144 RCLCPP_INFO(get_logger(), "✅ Subscribing Depth CameraInfo: %s",
145 topic_name_.c_str());
146
147 // Unknown topic_type error
148 } else {
149 RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
150 throw std::runtime_error("Unknown topic_type");
151 }
152 }
153
154 ~CameraTopicEcho() override {
155 if (video_writer_.isOpened()) {
156 video_writer_.release();
157 RCLCPP_INFO(get_logger(), "Video file closed.");
158 }
159 }
160
161private:
162 // PointCloud2 callback
163 void cb_pointcloud(const sensor_msgs::msg::PointCloud2::SharedPtr msg) {
164 // Update arrivals (for FPS calculation)
165 update_arrivals();
166
167 // Print point cloud basic info
168 if (should_print()) {
169 std::ostringstream oss;
170 oss << "🌫️ PointCloud2 received\n"
171 << " • frame_id: " << msg->header.frame_id << "\n"
172 << " • stamp (sec): "
173 << rclcpp::Time(msg->header.stamp).seconds() << "\n"
174 << " • width x height: " << msg->width << " x " << msg->height
175 << "\n"
176 << " • point_step: " << msg->point_step << "\n"
177 << " • row_step: " << msg->row_step << "\n"
178 << " • fields: ";
179 for (const auto &f : msg->fields)
180 oss << f.name << "(" << (int)f.datatype << ") ";
181 oss << "\n • is_bigendian: " << msg->is_bigendian
182 << "\n • is_dense: " << msg->is_dense
183 << "\n • data size: " << msg->data.size()
184 << "\n • recv FPS (1s): " << get_fps();
185 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
186 }
187 }
188
189 // Image callback (depth/RGB image)
190 void cb_image(const sensor_msgs::msg::Image::SharedPtr msg) {
191 update_arrivals();
192
193 if (should_print()) {
194 RCLCPP_INFO(get_logger(),
195 "📸 %s received\n"
196 " • frame_id: %s\n"
197 " • stamp (sec): %.6f\n"
198 " • encoding: %s\n"
199 " • size (WxH): %u x %u\n"
200 " • step (bytes/row):%u\n"
201 " • is_bigendian: %u\n"
202 " • recv FPS (1s): %.1f",
203 topic_type_.c_str(), msg->header.frame_id.c_str(),
204 rclcpp::Time(msg->header.stamp).seconds(),
205 msg->encoding.c_str(), msg->width, msg->height, msg->step,
206 msg->is_bigendian, get_fps());
207 }
208
209 // Video dump is supported only for RGB images
210 if (topic_type_ == "rgb_image" && !dump_video_path_.empty()) {
211 dump_image_to_video(msg);
212 }
213 }
214
215 // CompressedImage callback
216 void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
217 update_arrivals();
218
219 if (should_print()) {
220 RCLCPP_INFO(get_logger(),
221 "🗜️ CompressedImage received\n"
222 " • frame_id: %s\n"
223 " • stamp (sec): %.6f\n"
224 " • format: %s\n"
225 " • data size: %zu\n"
226 " • recv FPS (1s): %.1f",
227 msg->header.frame_id.c_str(),
228 rclcpp::Time(msg->header.stamp).seconds(),
229 msg->format.c_str(), msg->data.size(), get_fps());
230 }
231 }
232
233 // CameraInfo callback (camera intrinsic parameters)
234 void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
235 // CameraInfo is typically published once; print it once
236 std::ostringstream oss;
237 oss << "📷 " << topic_type_ << " received\n"
238 << " • frame_id: " << msg->header.frame_id << "\n"
239 << " • stamp (sec): " << rclcpp::Time(msg->header.stamp).seconds()
240 << "\n"
241 << " • width x height: " << msg->width << " x " << msg->height << "\n"
242 << " • distortion_model:" << msg->distortion_model << "\n"
243 << " • D: [";
244 for (size_t i = 0; i < msg->d.size(); ++i) {
245 oss << msg->d[i];
246 if (i + 1 < msg->d.size())
247 oss << ", ";
248 }
249 oss << "]\n • K: [";
250 for (int i = 0; i < 9; ++i) {
251 oss << msg->k[i];
252 if (i + 1 < 9)
253 oss << ", ";
254 }
255 oss << "]\n • R: [";
256 for (int i = 0; i < 9; ++i) {
257 oss << msg->r[i];
258 if (i + 1 < 9)
259 oss << ", ";
260 }
261 oss << "]\n • P: [";
262 for (int i = 0; i < 12; ++i) {
263 oss << msg->p[i];
264 if (i + 1 < 12)
265 oss << ", ";
266 }
267 oss << "]\n"
268 << " • binning_x: " << msg->binning_x << "\n"
269 << " • binning_y: " << msg->binning_y << "\n"
270 << " • roi: { x_offset: " << msg->roi.x_offset
271 << ", y_offset: " << msg->roi.y_offset
272 << ", height: " << msg->roi.height << ", width: " << msg->roi.width
273 << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
274 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
275 }
276
277 // Track arrival timestamps to compute FPS
278 void update_arrivals() {
279 const rclcpp::Time now = this->get_clock()->now();
280 arrivals_.push_back(now);
281 while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
282 arrivals_.pop_front();
283 }
284 }
285 double get_fps() const { return static_cast<double>(arrivals_.size()); }
286
287 // Control printing frequency
288 bool should_print() {
289 const rclcpp::Time now = this->get_clock()->now();
290 if ((now - last_print_).seconds() >= 1.0) {
291 last_print_ = now;
292 return true;
293 }
294 return false;
295 }
296
297 // Dump received images to a video file (RGB images only)
298 void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
299 cv::Mat image;
300 try {
301 // Obtain the Mat without copying by not specifying encoding
302 cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
303 image = cvp->image;
304 // Convert to BGR for uniform saving
305 if (msg->encoding == "rgb8") {
306 cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
307 } else {
308 RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
309 msg->encoding.c_str());
310 return;
311 }
312 } catch (const std::exception &e) {
313 RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
314 return;
315 }
316
317 // Initialize VideoWriter
318 if (!video_writer_.isOpened()) {
319 int fourcc = cv::VideoWriter::fourcc('M', 'J', 'P', 'G');
320 double fps = std::max(1.0, get_fps());
321 bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
322 cv::Size(image.cols, image.rows), true);
323 if (!ok) {
324 RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
325 dump_video_path_.c_str());
326 dump_video_path_.clear(); // stop trying
327 return;
328 }
329 RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
330 dump_video_path_.c_str(), image.cols, image.rows, fps);
331 }
332 video_writer_.write(image);
333 }
334
335 // Member variables
336 std::string topic_type_;
337 std::string topic_name_;
338 std::string dump_video_path_;
339
340 // Subscriptions
341 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
342 rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
343 sub_compressed_;
344 rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
345 rclcpp::Subscription<sensor_msgs::msg::PointCloud2>::SharedPtr
346 sub_pointcloud_;
347
348 // FPS statistics
349 rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
350 std::deque<rclcpp::Time> arrivals_;
351
352 // Video writer
353 cv::VideoWriter video_writer_;
354};
355
356int main(int argc, char **argv) {
357 rclcpp::init(argc, argv);
358 auto node = std::make_shared<CameraTopicEcho>();
359 rclcpp::spin(node);
360 rclcpp::shutdown();
361 return 0;
362}
使用说明:
订阅深度点云数据:
ros2 run examples echo_camera_rgbd --ros-args -p topic_type:=depth_pointcloud
订阅RGB图像数据:
ros2 run examples echo_camera_rgbd --ros-args -p topic_type:=rgb_image
订阅相机内参:
ros2 run examples echo_camera_rgbd --ros-args -p topic_type:=rgb_camera_info ros2 run examples echo_camera_rgbd --ros-args -p topic_type:=depth_camera_info
录制RGB视频:
# dump_video_path的值可改为其他路径, 注意提前创建该文件所在目录才能保存 ros2 run examples echo_camera_rgbd --ros-args -p topic_type:=rgb_image -p dump_video_path:=$PWD/output.avi
双目相机数据订阅
该示例中用到了echo_camera_stereo,通过订阅/aima/hal/sensor/stereo_head_front_*/话题来接收机器人的双目相机数据,支持左右相机的RGB图、压缩图和相机内参数据。
功能特点:
支持左右相机独立数据订阅
实时FPS统计和数据显示
支持RGB图视频录制功能
可配置的相机选择(左/右)
支持的数据类型:
left_rgb_image: 左相机RGB图像 (sensor_msgs/Image)left_rgb_image_compressed: 左相机压缩RGB图像 (sensor_msgs/CompressedImage)left_camera_info: 左相机内参 (sensor_msgs/CameraInfo)right_rgb_image: 右相机RGB图像 (sensor_msgs/Image)right_rgb_image_compressed: 右相机压缩RGB图像 (sensor_msgs/CompressedImage)right_camera_info: 右相机内参 (sensor_msgs/CameraInfo)
1#include <deque>
2#include <iomanip>
3#include <memory>
4#include <rclcpp/rclcpp.hpp>
5#include <sensor_msgs/msg/camera_info.hpp>
6#include <sensor_msgs/msg/compressed_image.hpp>
7#include <sensor_msgs/msg/image.hpp>
8#include <sstream>
9#include <string>
10#include <vector>
11
12// OpenCV headers for image/video writing
13#include <cv_bridge/cv_bridge.h>
14#include <opencv2/opencv.hpp>
15
16/**
17 * @brief Example of subscribing to multiple topics for the stereo head camera
18 *
19 * You can select which topic type to subscribe to via the startup argument
20 * --ros-args -p topic_type:=<type>:
21 * - left_rgb_image: left camera RGB image (sensor_msgs/Image)
22 * - left_rgb_image_compressed: left camera RGB compressed image
23 * (sensor_msgs/CompressedImage)
24 * - left_camera_info: left camera intrinsic parameters
25 * (sensor_msgs/CameraInfo)
26 * - right_rgb_image: right camera RGB image (sensor_msgs/Image)
27 * - right_rgb_image_compressed: right camera RGB compressed image
28 * (sensor_msgs/CompressedImage)
29 * - right_camera_info: right camera intrinsic parameters
30 * (sensor_msgs/CameraInfo)
31 *
32 * Examples:
33 * ros2 run examples echo_camera_stereo --ros-args -p
34 * topic_type:=left_rgb_image ros2 run examples echo_camera_stereo --ros-args -p
35 * topic_type:=right_rgb_image ros2 run examples echo_camera_stereo --ros-args
36 * -p topic_type:=left_camera_info
37 *
38 * topic_type defaults to "left_rgb_image"
39 *
40 * See individual callbacks for more detailed comments
41 */
42class StereoCameraTopicEcho : public rclcpp::Node {
43public:
44 StereoCameraTopicEcho() : Node("stereo_camera_topic_echo") {
45 // Select which topic type to subscribe to
46 topic_type_ =
47 declare_parameter<std::string>("topic_type", "left_rgb_image");
48 dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
49
50 // Subscribed topics and their message layouts
51 // 1. /aima/hal/sensor/stereo_head_front_left/rgb_image
52 // - topic_type: left_rgb_image
53 // - message type: sensor_msgs::msg::Image
54 // - frame_id: stereo_head_front
55 // - child_frame_id: /
56 // - contents: left camera raw image
57 // 2. /aima/hal/sensor/stereo_head_front_left/rgb_image/compressed
58 // - topic_type: left_rgb_image_compressed
59 // - message type: sensor_msgs::msg::CompressedImage
60 // - frame_id: stereo_head_front
61 // - contents: left camera compressed image
62 // 3. /aima/hal/sensor/stereo_head_front_left/camera_info
63 // - topic_type: left_camera_info
64 // - message type: sensor_msgs::msg::CameraInfo
65 // - frame_id: stereo_head_front
66 // - contents: left camera intrinsic parameters
67 // 4. /aima/hal/sensor/stereo_head_front_right/rgb_image
68 // - topic_type: right_rgb_image
69 // - message type: sensor_msgs::msg::Image
70 // - frame_id: stereo_head_front_right
71 // - child_frame_id: /
72 // - contents: right camera raw image
73 // 5. /aima/hal/sensor/stereo_head_front_right/rgb_image/compressed
74 // - topic_type: right_rgb_image_compressed
75 // - message type: sensor_msgs::msg::CompressedImage
76 // - frame_id: stereo_head_front_right
77 // - contents: right camera compressed image
78 // 6. /aima/hal/sensor/stereo_head_front_right/camera_info
79 // - topic_type: right_camera_info
80 // - message type: sensor_msgs::msg::CameraInfo
81 // - frame_id: stereo_head_front_right
82 // - contents: right camera intrinsic parameters
83
84 // Set QoS parameters - use SensorData QoS
85 auto qos = rclcpp::SensorDataQoS();
86
87 // Enable left camera RGB image subscription
88 if (topic_type_ == "left_rgb_image") {
89 topic_name_ = "/aima/hal/sensor/stereo_head_front_left/rgb_image";
90 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
91 topic_name_, qos,
92 std::bind(&StereoCameraTopicEcho::cb_image, this,
93 std::placeholders::_1));
94 RCLCPP_INFO(get_logger(), "✅ Subscribing Left RGB Image: %s",
95 topic_name_.c_str());
96 if (!dump_video_path_.empty()) {
97 RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
98 dump_video_path_.c_str());
99 }
100
101 // Enable left camera RGB compressed image subscription
102 } else if (topic_type_ == "left_rgb_image_compressed") {
103 topic_name_ =
104 "/aima/hal/sensor/stereo_head_front_left/rgb_image/compressed";
105 sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
106 topic_name_, qos,
107 std::bind(&StereoCameraTopicEcho::cb_compressed, this,
108 std::placeholders::_1));
109 RCLCPP_INFO(get_logger(), "✅ Subscribing Left CompressedImage: %s",
110 topic_name_.c_str());
111
112 // Enable left camera info subscription
113 } else if (topic_type_ == "left_camera_info") {
114
115 topic_name_ = "/aima/hal/sensor/stereo_head_front_left/camera_info";
116 // CameraInfo subscriptions must use reliable + transient_local
117 // QoS in order to receive latched/history messages (even if only one
118 // message was published). Here we use keep_last(1) + reliable
119 // + transient_local.
120 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
121 topic_name_,
122 rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
123 std::bind(&StereoCameraTopicEcho::cb_camerainfo, this,
124 std::placeholders::_1));
125 RCLCPP_INFO(get_logger(),
126 "✅ Subscribing Left CameraInfo (with transient_local): %s",
127 topic_name_.c_str());
128
129 // Enable right camera RGB image subscription
130 } else if (topic_type_ == "right_rgb_image") {
131 topic_name_ = "/aima/hal/sensor/stereo_head_front_right/rgb_image";
132 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
133 topic_name_, qos,
134 std::bind(&StereoCameraTopicEcho::cb_image, this,
135 std::placeholders::_1));
136 RCLCPP_INFO(get_logger(), "✅ Subscribing Right RGB Image: %s",
137 topic_name_.c_str());
138 if (!dump_video_path_.empty()) {
139 RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
140 dump_video_path_.c_str());
141 }
142
143 // Enable right camera RGB compressed image subscription
144 } else if (topic_type_ == "right_rgb_image_compressed") {
145 topic_name_ =
146 "/aima/hal/sensor/stereo_head_front_right/rgb_image/compressed";
147 sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
148 topic_name_, qos,
149 std::bind(&StereoCameraTopicEcho::cb_compressed, this,
150 std::placeholders::_1));
151 RCLCPP_INFO(get_logger(), "✅ Subscribing Right CompressedImage: %s",
152 topic_name_.c_str());
153
154 // Enable right camera info subscription
155 } else if (topic_type_ == "right_camera_info") {
156 topic_name_ = "/aima/hal/sensor/stereo_head_front_right/camera_info";
157 // CameraInfo subscriptions must use reliable + transient_local
158 // QoS in order to receive latched/history messages (even if only one
159 // message was published). Here we use keep_last(1) + reliable
160 // + transient_local.
161 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
162 topic_name_,
163 rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
164 std::bind(&StereoCameraTopicEcho::cb_camerainfo, this,
165 std::placeholders::_1));
166 RCLCPP_INFO(get_logger(),
167 "✅ Subscribing Right CameraInfo (with transient_local): %s",
168 topic_name_.c_str());
169
170 // Unknown topic_type error
171 } else {
172 RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
173 throw std::runtime_error("Unknown topic_type");
174 }
175 }
176
177 ~StereoCameraTopicEcho() override {
178 if (video_writer_.isOpened()) {
179 video_writer_.release();
180 RCLCPP_INFO(get_logger(), "Video file closed.");
181 }
182 }
183
184private:
185 // Image callback (left/right RGB image)
186 void cb_image(const sensor_msgs::msg::Image::SharedPtr msg) {
187 update_arrivals();
188
189 if (should_print()) {
190 RCLCPP_INFO(get_logger(),
191 "📸 %s received\n"
192 " • frame_id: %s\n"
193 " • stamp (sec): %.6f\n"
194 " • encoding: %s\n"
195 " • size (WxH): %u x %u\n"
196 " • step (bytes/row):%u\n"
197 " • is_bigendian: %u\n"
198 " • recv FPS (1s): %.1f",
199 topic_type_.c_str(), msg->header.frame_id.c_str(),
200 rclcpp::Time(msg->header.stamp).seconds(),
201 msg->encoding.c_str(), msg->width, msg->height, msg->step,
202 msg->is_bigendian, get_fps());
203 }
204
205 // Video dump is supported only for RGB images
206 if ((topic_type_ == "left_rgb_image" || topic_type_ == "right_rgb_image") &&
207 !dump_video_path_.empty()) {
208 dump_image_to_video(msg);
209 }
210 }
211
212 // CompressedImage callback (left/right RGB compressed image)
213 void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
214 update_arrivals();
215
216 if (should_print()) {
217 RCLCPP_INFO(get_logger(),
218 "🗜️ %s received\n"
219 " • frame_id: %s\n"
220 " • stamp (sec): %.6f\n"
221 " • format: %s\n"
222 " • data size: %zu\n"
223 " • recv FPS (1s): %.1f",
224 topic_type_.c_str(), msg->header.frame_id.c_str(),
225 rclcpp::Time(msg->header.stamp).seconds(),
226 msg->format.c_str(), msg->data.size(), get_fps());
227 }
228 }
229
230 // CameraInfo callback (left/right camera intrinsic parameters)
231 void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
232 // CameraInfo is typically published once; print it once
233 std::ostringstream oss;
234 oss << "📷 " << topic_type_ << " received\n"
235 << " • frame_id: " << msg->header.frame_id << "\n"
236 << " • stamp (sec): " << rclcpp::Time(msg->header.stamp).seconds()
237 << "\n"
238 << " • width x height: " << msg->width << " x " << msg->height << "\n"
239 << " • distortion_model:" << msg->distortion_model << "\n"
240 << " • D: [";
241 for (size_t i = 0; i < msg->d.size(); ++i) {
242 oss << msg->d[i];
243 if (i + 1 < msg->d.size())
244 oss << ", ";
245 }
246 oss << "]\n • K: [";
247 for (int i = 0; i < 9; ++i) {
248 oss << msg->k[i];
249 if (i + 1 < 9)
250 oss << ", ";
251 }
252 oss << "]\n • R: [";
253 for (int i = 0; i < 9; ++i) {
254 oss << msg->r[i];
255 if (i + 1 < 9)
256 oss << ", ";
257 }
258 oss << "]\n • P: [";
259 for (int i = 0; i < 12; ++i) {
260 oss << msg->p[i];
261 if (i + 1 < 12)
262 oss << ", ";
263 }
264 oss << "]\n"
265 << " • binning_x: " << msg->binning_x << "\n"
266 << " • binning_y: " << msg->binning_y << "\n"
267 << " • roi: { x_offset: " << msg->roi.x_offset
268 << ", y_offset: " << msg->roi.y_offset
269 << ", height: " << msg->roi.height << ", width: " << msg->roi.width
270 << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
271 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
272 }
273
274 // Track arrival timestamps to compute FPS
275 void update_arrivals() {
276 const rclcpp::Time now = this->get_clock()->now();
277 arrivals_.push_back(now);
278 while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
279 arrivals_.pop_front();
280 }
281 }
282 double get_fps() const { return static_cast<double>(arrivals_.size()); }
283
284 // Control printing frequency
285 bool should_print() {
286 const rclcpp::Time now = this->get_clock()->now();
287 if ((now - last_print_).seconds() >= 1.0) {
288 last_print_ = now;
289 return true;
290 }
291 return false;
292 }
293
294 // Dump received images to a video file (RGB images only)
295 void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
296 cv::Mat image;
297 try {
298 // Obtain the Mat without copying by not specifying encoding
299 cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
300 image = cvp->image;
301 // Convert to BGR for uniform saving
302 if (msg->encoding == "rgb8") {
303 cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
304 } else {
305 RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
306 msg->encoding.c_str());
307 return;
308 }
309 } catch (const std::exception &e) {
310 RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
311 return;
312 }
313
314 // Initialize VideoWriter
315 if (!video_writer_.isOpened()) {
316 int fourcc = cv::VideoWriter::fourcc('M', 'J', 'P', 'G');
317 double fps = std::max(1.0, get_fps());
318 bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
319 cv::Size(image.cols, image.rows), true);
320 if (!ok) {
321 RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
322 dump_video_path_.c_str());
323 dump_video_path_.clear(); // stop trying
324 return;
325 }
326 RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
327 dump_video_path_.c_str(), image.cols, image.rows, fps);
328 }
329 video_writer_.write(image);
330 }
331
332 // Member variables
333 std::string topic_type_;
334 std::string topic_name_;
335 std::string dump_video_path_;
336
337 // Subscriptions
338 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
339 rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
340 sub_compressed_;
341 rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
342
343 // FPS statistics
344 rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
345 std::deque<rclcpp::Time> arrivals_;
346
347 // Video writer
348 cv::VideoWriter video_writer_;
349};
350
351int main(int argc, char **argv) {
352 rclcpp::init(argc, argv);
353 auto node = std::make_shared<StereoCameraTopicEcho>();
354 rclcpp::spin(node);
355 rclcpp::shutdown();
356 return 0;
357}
使用说明:
订阅左相机RGB图像:
ros2 run examples echo_camera_stereo --ros-args -p topic_type:=left_rgb_image
订阅右相机RGB图像:
ros2 run examples echo_camera_stereo --ros-args -p topic_type:=right_rgb_image
订阅左相机内参:
ros2 run examples echo_camera_stereo --ros-args -p topic_type:=left_camera_info
录制左相机视频:
# dump_video_path的值可改为其他路径, 注意提前创建该文件所在目录才能保存 ros2 run examples echo_camera_stereo --ros-args -p topic_type:=left_rgb_image -p dump_video_path:=$PWD/left_camera.avi
头部后置单目相机数据订阅
该示例中用到了echo_camera_head_rear,通过订阅/aima/hal/sensor/rgb_head_rear/话题来接收机器人的头部后置单目相机数据,支持RGB图(及其mask处理)、压缩图和相机内参数据。
功能特点:
支持头部后置相机数据订阅
实时FPS统计和数据显示
支持RGB图视频录制功能及把手遮挡区域mask处理
可配置的topic类型选择
支持的数据类型:
rgb_image: RGB图像 (sensor_msgs/Image)rgb_image_compressed: 压缩RGB图像 (sensor_msgs/CompressedImage)camera_info: 相机内参 (sensor_msgs/CameraInfo)
1#include <deque>
2#include <filesystem>
3#include <iomanip>
4#include <memory>
5#include <rclcpp/rclcpp.hpp>
6#include <sensor_msgs/msg/camera_info.hpp>
7#include <sensor_msgs/msg/compressed_image.hpp>
8#include <sensor_msgs/msg/image.hpp>
9#include <sstream>
10#include <string>
11#include <vector>
12
13// OpenCV headers for image/video writing
14#include <cv_bridge/cv_bridge.h>
15#include <opencv2/opencv.hpp>
16
17/**
18 * @brief Example of subscribing to multiple topics for the rear head monocular
19 * camera
20 *
21 * You can select which topic type to subscribe to via the startup argument
22 * --ros-args -p topic_type:=<type>:
23 * - rgb_image: RGB image (sensor_msgs/Image)
24 * - rgb_image_compressed: RGB compressed image (sensor_msgs/CompressedImage)
25 * - camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
26 *
27 * Examples:
28 * ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image
29 * ros2 run examples echo_camera_head_rear --ros-args -p
30 * topic_type:=rgb_image_compressed ros2 run examples echo_camera_head_rear
31 * --ros-args -p topic_type:=camera_info
32 *
33 * topic_type defaults to "rgb_image"
34 *
35 * See individual callbacks for more detailed comments
36 */
37class HeadRearCameraTopicEcho : public rclcpp::Node {
38public:
39 HeadRearCameraTopicEcho() : Node("head_rear_camera_topic_echo") {
40 // Select which topic type to subscribe to
41 topic_type_ = declare_parameter<std::string>("topic_type", "rgb_image");
42 dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
43 with_mask_ = declare_parameter<bool>("with_mask", false);
44
45 // Subscribed topics and their message layouts
46 // 1. /aima/hal/sensor/rgb_head_rear/rgb_image
47 // - topic_type: rgb_image
48 // - message type: sensor_msgs::msg::Image
49 // - frame_id: rgb_head_rear
50 // - child_frame_id: /
51 // - contents: raw image data
52 // 2. /aima/hal/sensor/rgb_head_rear/rgb_image/compressed
53 // - topic_type: rgb_image_compressed
54 // - message type: sensor_msgs::msg::CompressedImage
55 // - frame_id: rgb_head_rear
56 // - contents: compressed image data
57 // 3. /aima/hal/sensor/rgb_head_rear/camera_info
58 // - topic_type: camera_info
59 // - message type: sensor_msgs::msg::CameraInfo
60 // - frame_id: rgb_head_rear
61 // - contents: camera intrinsic parameters
62
63 // Set QoS parameters - use SensorData QoS
64 auto qos = rclcpp::SensorDataQoS();
65
66 if (with_mask_ && !dump_video_path_.empty()) {
67 auto mask_path =
68 std::filesystem::read_symlink("/proc/self/exe").parent_path() /
69 "data" / "rgb_head_rear_mask.png";
70 mask_image_ = cv::imread(mask_path, cv::IMREAD_GRAYSCALE);
71 if (mask_image_.empty()) {
72 RCLCPP_ERROR(get_logger(), "Failed to load mask file from %s",
73 mask_path.c_str());
74 throw std::runtime_error("Failed to load mask file");
75 }
76 }
77
78 // Enable RGB image subscription
79 if (topic_type_ == "rgb_image") {
80 topic_name_ = "/aima/hal/sensor/rgb_head_rear/rgb_image";
81 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
82 topic_name_, qos,
83 std::bind(&HeadRearCameraTopicEcho::cb_image, this,
84 std::placeholders::_1));
85 RCLCPP_INFO(get_logger(), "✅ Subscribing RGB Image: %s",
86 topic_name_.c_str());
87 if (!dump_video_path_.empty()) {
88 RCLCPP_INFO(
89 get_logger(), "📝 Will dump received images %s mask to video: %s",
90 (with_mask_ ? "with" : "without"), dump_video_path_.c_str());
91 }
92 }
93
94 // Enable RGB compressed image subscription
95 else if (topic_type_ == "rgb_image_compressed") {
96 topic_name_ = "/aima/hal/sensor/rgb_head_rear/rgb_image/compressed";
97 sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
98 topic_name_, qos,
99 std::bind(&HeadRearCameraTopicEcho::cb_compressed, this,
100 std::placeholders::_1));
101 RCLCPP_INFO(get_logger(), "✅ Subscribing CompressedImage: %s",
102 topic_name_.c_str());
103
104 // Enable camera info subscription
105 } else if (topic_type_ == "camera_info") {
106 topic_name_ = "/aima/hal/sensor/rgb_head_rear/camera_info";
107 // CameraInfo subscriptions must use reliable + transient_local
108 // QoS in order to receive latched/history messages (even if only one
109 // message was published). Here we use keep_last(1) + reliable
110 // + transient_local.
111 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
112 topic_name_,
113 rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
114 std::bind(&HeadRearCameraTopicEcho::cb_camerainfo, this,
115 std::placeholders::_1));
116 RCLCPP_INFO(get_logger(),
117 "✅ Subscribing CameraInfo (with transient_local): %s",
118 topic_name_.c_str());
119
120 // Unknown topic_type error
121 } else {
122 RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
123 throw std::runtime_error("Unknown topic_type");
124 }
125 }
126
127 ~HeadRearCameraTopicEcho() override {
128 if (video_writer_.isOpened()) {
129 video_writer_.release();
130 RCLCPP_INFO(get_logger(), "Video file closed.");
131 }
132 }
133
134private:
135 // Image callback (RGB image)
136 void cb_image(const sensor_msgs::msg::Image::SharedPtr msg) {
137 update_arrivals();
138
139 if (should_print()) {
140 RCLCPP_INFO(get_logger(),
141 "📸 %s received\n"
142 " • frame_id: %s\n"
143 " • stamp (sec): %.6f\n"
144 " • encoding: %s\n"
145 " • size (WxH): %u x %u\n"
146 " • step (bytes/row):%u\n"
147 " • is_bigendian: %u\n"
148 " • recv FPS (1s): %.1f",
149 topic_type_.c_str(), msg->header.frame_id.c_str(),
150 rclcpp::Time(msg->header.stamp).seconds(),
151 msg->encoding.c_str(), msg->width, msg->height, msg->step,
152 msg->is_bigendian, get_fps());
153 }
154
155 // Video dump is supported only for RGB images
156 if (topic_type_ == "rgb_image" && !dump_video_path_.empty()) {
157 dump_image_to_video(msg);
158 }
159 }
160
161 // CompressedImage callback (RGB compressed image)
162 void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
163 update_arrivals();
164
165 if (should_print()) {
166 RCLCPP_INFO(get_logger(),
167 "🗜️ %s received\n"
168 " • frame_id: %s\n"
169 " • stamp (sec): %.6f\n"
170 " • format: %s\n"
171 " • data size: %zu\n"
172 " • recv FPS (1s): %.1f",
173 topic_type_.c_str(), msg->header.frame_id.c_str(),
174 rclcpp::Time(msg->header.stamp).seconds(),
175 msg->format.c_str(), msg->data.size(), get_fps());
176 }
177 }
178
179 // CameraInfo callback (camera intrinsic parameters)
180 void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
181 // CameraInfo is typically published once; print it once
182 std::ostringstream oss;
183 oss << "📷 " << topic_type_ << " received\n"
184 << " • frame_id: " << msg->header.frame_id << "\n"
185 << " • stamp (sec): " << rclcpp::Time(msg->header.stamp).seconds()
186 << "\n"
187 << " • width x height: " << msg->width << " x " << msg->height << "\n"
188 << " • distortion_model:" << msg->distortion_model << "\n"
189 << " • D: [";
190 for (size_t i = 0; i < msg->d.size(); ++i) {
191 oss << msg->d[i];
192 if (i + 1 < msg->d.size())
193 oss << ", ";
194 }
195 oss << "]\n • K: [";
196 for (int i = 0; i < 9; ++i) {
197 oss << msg->k[i];
198 if (i + 1 < 9)
199 oss << ", ";
200 }
201 oss << "]\n • R: [";
202 for (int i = 0; i < 9; ++i) {
203 oss << msg->r[i];
204 if (i + 1 < 9)
205 oss << ", ";
206 }
207 oss << "]\n • P: [";
208 for (int i = 0; i < 12; ++i) {
209 oss << msg->p[i];
210 if (i + 1 < 12)
211 oss << ", ";
212 }
213 oss << "]\n"
214 << " • binning_x: " << msg->binning_x << "\n"
215 << " • binning_y: " << msg->binning_y << "\n"
216 << " • roi: { x_offset: " << msg->roi.x_offset
217 << ", y_offset: " << msg->roi.y_offset
218 << ", height: " << msg->roi.height << ", width: " << msg->roi.width
219 << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
220 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
221 }
222
223 // Track arrival timestamps to compute FPS
224 void update_arrivals() {
225 const rclcpp::Time now = this->get_clock()->now();
226 arrivals_.push_back(now);
227 while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
228 arrivals_.pop_front();
229 }
230 }
231 double get_fps() const { return static_cast<double>(arrivals_.size()); }
232
233 // Control printing frequency
234 bool should_print() {
235 const rclcpp::Time now = this->get_clock()->now();
236 if ((now - last_print_).seconds() >= 1.0) {
237 last_print_ = now;
238 return true;
239 }
240 return false;
241 }
242
243 // Dump received images to a video file (RGB images only)
244 void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
245 cv::Mat image;
246 try {
247 // Obtain the Mat without copying by not specifying encoding
248 cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
249 image = cvp->image;
250 // Convert to BGR for uniform saving
251 if (msg->encoding == "rgb8") {
252 cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
253 } else {
254 RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
255 msg->encoding.c_str());
256 return;
257 }
258 if (with_mask_) {
259 image.setTo(cv::Scalar(0, 0, 0), mask_image_ == 0);
260 }
261 } catch (const std::exception &e) {
262 RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
263 return;
264 }
265
266 // Initialize VideoWriter
267 if (!video_writer_.isOpened()) {
268 int fourcc = cv::VideoWriter::fourcc('M', 'J', 'P', 'G');
269 double fps = std::max(1.0, get_fps());
270 bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
271 cv::Size(image.cols, image.rows), true);
272 if (!ok) {
273 RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
274 dump_video_path_.c_str());
275 dump_video_path_.clear(); // stop trying
276 return;
277 }
278 RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
279 dump_video_path_.c_str(), image.cols, image.rows, fps);
280 }
281 video_writer_.write(image);
282 }
283
284 // Member variables
285 std::string topic_type_;
286 std::string topic_name_;
287 std::string dump_video_path_;
288 bool with_mask_;
289 cv::Mat mask_image_;
290
291 // Subscriptions
292 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
293 rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
294 sub_compressed_;
295 rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
296
297 // FPS statistics
298 rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
299 std::deque<rclcpp::Time> arrivals_;
300
301 // Video writer
302 cv::VideoWriter video_writer_;
303};
304
305int main(int argc, char **argv) {
306 rclcpp::init(argc, argv);
307 auto node = std::make_shared<HeadRearCameraTopicEcho>();
308 rclcpp::spin(node);
309 rclcpp::shutdown();
310 return 0;
311}
使用说明:
订阅RGB图像数据:
ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image
订阅压缩图像数据:
ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image_compressed
订阅相机内参:
ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=camera_info
录制视频:
# dump_video_path的值可改为其他路径, 注意提前创建该文件所在目录才能保存 ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image -p dump_video_path:=$PWD/rear_camera.avi
录制视频并对把手遮挡区域mask处理:
# dump_video_path的值可改为其他路径, 注意提前创建该文件所在目录才能保存 ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image -p with_mask:=true -p dump_video_path:=$PWD/rear_camera.avi
应用场景:
人脸识别和追踪
目标检测和识别
视觉SLAM
图像处理和计算机视觉算法开发
机器人视觉导航
6.2.13 头部触摸传感器数据订阅
该示例中用到了 echo_head_touch_sensor,通过订阅/aima/hal/sensor/touch_head话题来接收机器人的头部触摸传感器的反馈数据。
功能特点:
订阅了头部传感器的反馈数据,当头部被触摸时候,输出会从 IDLE->TOUCH
1//
2// Created by agiuser on 2026/1/23.
3//
4
5#include <aimdk_msgs/msg/touch_state.hpp>
6#include <rclcpp/rclcpp.hpp>
7
8class TouchStateSubscriber : public rclcpp::Node {
9public:
10 TouchStateSubscriber() : Node("touch_state_subscriber") {
11 subscription_ = this->create_subscription<aimdk_msgs::msg::TouchState>(
12 "/aima/hal/sensor/touch_head", 10,
13 std::bind(&TouchStateSubscriber::touch_callback, this,
14 std::placeholders::_1));
15
16 RCLCPP_INFO(this->get_logger(), "TouchState subscriber started, listening "
17 "to /aima/hal/sensor/touch_head");
18 }
19
20private:
21 void touch_callback(const aimdk_msgs::msg::TouchState::SharedPtr msg) {
22 // print message info
23 RCLCPP_INFO(this->get_logger(), "Received TouchState message:");
24 RCLCPP_INFO(this->get_logger(), " Timestamp: %d.%09d",
25 msg->header.stamp.sec, msg->header.stamp.nanosec);
26
27 std::string event_str = get_event_type_string(msg->event_type);
28 RCLCPP_INFO(this->get_logger(), " Event Type: %s (%d)", event_str.c_str(),
29 msg->event_type);
30 }
31
32 std::string get_event_type_string(uint8_t event_type) {
33 switch (event_type) {
34 case aimdk_msgs::msg::TouchState::UNKNOWN:
35 return "UNKNOWN";
36 case aimdk_msgs::msg::TouchState::IDLE:
37 return "IDLE";
38 case aimdk_msgs::msg::TouchState::TOUCH:
39 return "TOUCH";
40 case aimdk_msgs::msg::TouchState::SLIDE:
41 return "SLIDE";
42 case aimdk_msgs::msg::TouchState::PAT_ONCE:
43 return "PAT_ONCE";
44 case aimdk_msgs::msg::TouchState::PAT_TWICE:
45 return "PAT_TWICE";
46 case aimdk_msgs::msg::TouchState::PAT_TRIPLE:
47 return "PAT_TRIPLE";
48 default:
49 return "INVALID";
50 }
51 }
52 rclcpp::Subscription<aimdk_msgs::msg::TouchState>::SharedPtr subscription_;
53};
54
55int main(int argc, char *argv[]) {
56 rclcpp::init(argc, argv);
57 auto node = std::make_shared<TouchStateSubscriber>();
58 rclcpp::spin(node);
59 rclcpp::shutdown();
60 return 0;
61}
使用说明:
ros2 ros2 run examples echo_head_touch_sensor
输出示例:
[INFO] [1769162721.359354722] [touch_state_subscriber]: Timestamp: 1769162726.863282315
[INFO] [1769162721.359361643] [touch_state_subscriber]: Event Type: IDLE (1)
[INFO] [1769167184.142143492] [touch_state_subscriber]: Timestamp: 1769167189.364879133
[INFO] [1769167184.142147126] [touch_state_subscriber]: Event Type: TOUCH (2)
6.2.14 激光雷达数据订阅
该示例中用到了echo_lidar_data,通过订阅/aima/hal/sensor/lidar_chest_front/话题来接收机器人的激光雷达数据,支持点云数据和IMU数据两种数据类型。
功能特点:
支持激光雷达点云数据订阅
支持激光雷达IMU数据订阅
实时FPS统计和数据显示
可配置的topic类型选择
详细的数据字段信息输出
支持的数据类型:
PointCloud2: 激光雷达点云数据 (sensor_msgs/PointCloud2)Imu: 激光雷达IMU数据 (sensor_msgs/Imu)
技术实现:
使用SensorDataQoS配置(
BEST_EFFORT+VOLATILE)支持点云字段信息解析和显示
支持IMU四元数、角速度和线性加速度数据
提供详细的调试日志输出
应用场景:
激光雷达数据采集和分析
点云数据处理和可视化
机器人导航和定位
SLAM算法开发
环境感知和建图
1#include <deque>
2#include <iomanip>
3#include <memory>
4#include <rclcpp/rclcpp.hpp>
5#include <sensor_msgs/msg/imu.hpp>
6#include <sensor_msgs/msg/point_cloud2.hpp>
7#include <sstream>
8#include <string>
9#include <vector>
10
11/**
12 * @brief Example for subscribing to chest LIDAR data
13 *
14 * Supports subscribing to the following topics:
15 * 1. /aima/hal/sensor/lidar_chest_front/lidar_pointcloud
16 * - Data type: sensor_msgs::msg::PointCloud2
17 * - frame_id: lidar_chest_front
18 * - child_frame_id: /
19 * - Content: LIDAR point cloud data
20 * 2. /aima/hal/sensor/lidar_chest_front/imu
21 * - Data type: sensor_msgs::msg::Imu
22 * - frame_id: lidar_imu_chest_front
23 * - Content: LIDAR IMU data
24 *
25 * You can select the topic type to subscribe to using the launch parameter
26 * --ros-args -p topic_type:=<type>:
27 * - pointcloud: Subscribe to LIDAR point cloud
28 * - imu: Subscribe to LIDAR IMU
29 * The default topic_type is pointcloud
30 */
31class LidarChestEcho : public rclcpp::Node {
32public:
33 LidarChestEcho() : Node("lidar_chest_echo") {
34 topic_type_ = declare_parameter<std::string>("topic_type", "pointcloud");
35
36 auto qos = rclcpp::SensorDataQoS();
37
38 if (topic_type_ == "pointcloud") {
39 topic_name_ = "/aima/hal/sensor/lidar_chest_front/lidar_pointcloud";
40 sub_pointcloud_ = create_subscription<sensor_msgs::msg::PointCloud2>(
41 topic_name_, qos,
42 std::bind(&LidarChestEcho::cb_pointcloud, this,
43 std::placeholders::_1));
44 RCLCPP_INFO(get_logger(), "✅ Subscribing LIDAR PointCloud2: %s",
45 topic_name_.c_str());
46 } else if (topic_type_ == "imu") {
47 topic_name_ = "/aima/hal/sensor/lidar_chest_front/imu";
48 sub_imu_ = create_subscription<sensor_msgs::msg::Imu>(
49 topic_name_, qos,
50 std::bind(&LidarChestEcho::cb_imu, this, std::placeholders::_1));
51 RCLCPP_INFO(get_logger(), "✅ Subscribing LIDAR IMU: %s",
52 topic_name_.c_str());
53 } else {
54 RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
55 throw std::runtime_error("Unknown topic_type");
56 }
57 }
58
59private:
60 // PointCloud2 callback
61 void cb_pointcloud(const sensor_msgs::msg::PointCloud2::SharedPtr msg) {
62 update_arrivals();
63
64 if (should_print()) {
65 std::ostringstream oss;
66 oss << "🟢 LIDAR PointCloud2 received\n"
67 << " • frame_id: " << msg->header.frame_id << "\n"
68 << " • stamp (sec): "
69 << rclcpp::Time(msg->header.stamp).seconds() << "\n"
70 << " • width x height: " << msg->width << " x " << msg->height
71 << "\n"
72 << " • point_step: " << msg->point_step << "\n"
73 << " • row_step: " << msg->row_step << "\n"
74 << " • fields: ";
75 for (const auto &f : msg->fields)
76 oss << f.name << "(" << (int)f.datatype << ") ";
77 oss << "\n • is_bigendian: " << msg->is_bigendian
78 << "\n • is_dense: " << msg->is_dense
79 << "\n • data size: " << msg->data.size()
80 << "\n • recv FPS (1s): " << get_fps();
81 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
82 }
83 }
84
85 // IMU callback
86 void cb_imu(const sensor_msgs::msg::Imu::SharedPtr msg) {
87 update_arrivals();
88
89 if (should_print()) {
90 std::ostringstream oss;
91 oss << "🟢 LIDAR IMU received\n"
92 << " • frame_id: " << msg->header.frame_id << "\n"
93 << " • stamp (sec): "
94 << rclcpp::Time(msg->header.stamp).seconds() << "\n"
95 << " • orientation: [" << msg->orientation.x << ", "
96 << msg->orientation.y << ", " << msg->orientation.z << ", "
97 << msg->orientation.w << "]\n"
98 << " • angular_velocity:[" << msg->angular_velocity.x << ", "
99 << msg->angular_velocity.y << ", " << msg->angular_velocity.z << "]\n"
100 << " • linear_accel: [" << msg->linear_acceleration.x << ", "
101 << msg->linear_acceleration.y << ", " << msg->linear_acceleration.z
102 << "]\n"
103 << " • recv FPS (1s): " << get_fps();
104 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
105 }
106 }
107
108 // Update FPS statistics
109 void update_arrivals() {
110 const rclcpp::Time now = this->get_clock()->now();
111 arrivals_.push_back(now);
112 while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
113 arrivals_.pop_front();
114 }
115 }
116 double get_fps() const { return static_cast<double>(arrivals_.size()); }
117
118 // Control print frequency
119 bool should_print() {
120 const rclcpp::Time now = this->get_clock()->now();
121 if ((now - last_print_).seconds() >= 1.0) {
122 last_print_ = now;
123 return true;
124 }
125 return false;
126 }
127
128 // Member variables
129 std::string topic_type_;
130 std::string topic_name_;
131
132 rclcpp::Subscription<sensor_msgs::msg::PointCloud2>::SharedPtr
133 sub_pointcloud_;
134 rclcpp::Subscription<sensor_msgs::msg::Imu>::SharedPtr sub_imu_;
135
136 rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
137 std::deque<rclcpp::Time> arrivals_;
138};
139
140int main(int argc, char **argv) {
141 rclcpp::init(argc, argv);
142 auto node = std::make_shared<LidarChestEcho>();
143 rclcpp::spin(node);
144 rclcpp::shutdown();
145 return 0;
146}
使用说明:
# 订阅激光雷达点云数据
ros2 run examples echo_lidar_data --ros-args -p topic_type:=pointcloud
# 订阅激光雷达IMU数据
ros2 run examples echo_lidar_data --ros-args -p topic_type:=imu
输出示例:
[INFO] [lidar_chest_echo]: ✅ Subscribing LIDAR PointCloud2: /aima/hal/sensor/lidar_chest_front/lidar_pointcloud
[INFO] [lidar_chest_echo]: 🟢 LIDAR PointCloud2 received
• frame_id: lidar_chest_front
• stamp (sec): 1234567890.123456
• width x height: 1 x 36000
• point_step: 16
• row_step: 16
• fields: x(7) y(7) z(7) intensity(7)
• is_bigendian: False
• is_dense: True
• data size: 576000
• recv FPS (1s): 10.0
6.2.15 播放视频
该示例中用到了play_video,在运行节点程序前,需要先将视频上传到机器人的**交互计算单元(PC3)**上(用户可在其上创建一个用来存储视频的目录如/var/tmp/videos/),然后将节点程序中的video_path改为需要播放视频的路径。
注意
⚠️ 请注意!交互计算单元(PC3)独立于二开程序所在的开发计算单元(PC2), 音视频文件务必存入交互计算单元(IP: 10.0.1.42)。
⚠️ 音视频文件夹及该文件夹所有父目录应当为所有用户可访问读取(建议在/var/tmp/下创建子目录存放)
功能说明
通过调用PlayVideo服务,可以让机器人在屏幕上播放指定路径的视频文件。请确保视频文件已上传到交互计算单元,否则播放会失败。
1#include "aimdk_msgs/srv/play_video.hpp"
2#include "aimdk_msgs/msg/common_request.hpp"
3#include "rclcpp/rclcpp.hpp"
4#include <chrono>
5#include <memory>
6#include <signal.h>
7#include <string>
8
9// Global variable used for signal handling
10std::shared_ptr<rclcpp::Node> g_node = nullptr;
11
12// Signal handler function
13void signal_handler(int signal) {
14 if (g_node) {
15 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
16 signal);
17 g_node.reset();
18 }
19 rclcpp::shutdown();
20 exit(signal);
21}
22
23class PlayVideoClient : public rclcpp::Node {
24public:
25 PlayVideoClient() : Node("play_video_client") {
26 client_ = this->create_client<aimdk_msgs::srv::PlayVideo>(
27 "/face_ui_proxy/play_video");
28 RCLCPP_INFO(this->get_logger(), "✅ PlayVideo client node started.");
29
30 // Wait for the service to become available
31 while (!client_->wait_for_service(std::chrono::seconds(2))) {
32 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
33 }
34 RCLCPP_INFO(this->get_logger(),
35 "🟢 Service available, ready to send request.");
36 }
37
38 bool send_request(const std::string &video_path, uint8_t mode,
39 int32_t priority) {
40 try {
41 auto request = std::make_shared<aimdk_msgs::srv::PlayVideo::Request>();
42
43 request->video_path = video_path;
44 request->mode = mode;
45 request->priority = priority;
46
47 RCLCPP_INFO(this->get_logger(),
48 "📨 Sending request to play video: mode=%hhu video=%s", mode,
49 video_path.c_str());
50
51 const std::chrono::milliseconds timeout(250);
52 for (int i = 0; i < 8; i++) {
53 request->header.header.stamp = this->now();
54 auto future = client_->async_send_request(request);
55 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
56 future, timeout);
57 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
58 // retry as remote peer is NOT handled well by ROS
59 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
60 continue;
61 }
62 // future.done
63 auto response = future.get();
64 if (response->success) {
65 RCLCPP_INFO(this->get_logger(),
66 "✅ Request to play video recorded successfully: %s",
67 response->message.c_str());
68 return true;
69 } else {
70 RCLCPP_ERROR(this->get_logger(),
71 "❌ Failed to record play-video request: %s",
72 response->message.c_str());
73 return false;
74 }
75 }
76 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
77 return false;
78 } catch (const std::exception &e) {
79 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
80 return false;
81 }
82 }
83
84private:
85 rclcpp::Client<aimdk_msgs::srv::PlayVideo>::SharedPtr client_;
86};
87
88int main(int argc, char **argv) {
89 try {
90 rclcpp::init(argc, argv);
91
92 // Set up signal handlers
93 signal(SIGINT, signal_handler);
94 signal(SIGTERM, signal_handler);
95
96 std::string video_path =
97 "/agibot/data/home/agi/zhiyuan.mp4"; // Default video path; modify as
98 // needed
99 int32_t priority = 5;
100 int mode = 2; // Loop playback
101 std::cout << "Enter video play mode (1: once, 2: loop): ";
102 std::cin >> mode;
103 if (mode < 1 || mode > 2) {
104 RCLCPP_ERROR(rclcpp::get_logger("main"), "Invalid play mode: %d", mode);
105 rclcpp::shutdown();
106 return 1;
107 }
108
109 g_node = std::make_shared<PlayVideoClient>();
110 auto client = std::dynamic_pointer_cast<PlayVideoClient>(g_node);
111
112 if (client) {
113 client->send_request(video_path, mode, priority);
114 }
115
116 // Clean up resources
117 g_node.reset();
118 rclcpp::shutdown();
119
120 return 0;
121 } catch (const std::exception &e) {
122 RCLCPP_ERROR(rclcpp::get_logger("main"),
123 "Program exited with exception: %s", e.what());
124 return 1;
125 }
126}
6.2.16 媒体文件播放
该示例中用到了play_media,通过该节点可实现播放指定的媒体文件(如音频文件),支持WAV、MP3等格式的音频文件播放。
功能特点:
支持多种音频格式播放(WAV、MP3等)
支持优先级控制,可设置播放优先级
支持打断机制,可中断当前播放
支持自定义文件路径和播放参数
提供完整的错误处理和状态反馈
技术实现:
使用PlayMediaFile服务进行媒体文件播放
支持优先级级别设置(0-99)
支持中断控制(is_interrupted参数)
提供详细的播放状态反馈
应用场景:
音频文件播放和媒体控制
语音提示和音效播放
多媒体应用开发
机器人交互音频反馈
注意
⚠️ 请注意!交互计算单元(PC3)独立于二开程序所在的开发计算单元(PC2), 音视频文件务必存入交互计算单元(IP: 10.0.1.42)。
⚠️ 音视频文件夹及该文件夹所有父目录应当为所有用户可访问读取(建议在/var/tmp/下创建子目录存放)
1#include <aimdk_msgs/msg/tts_priority_level.hpp>
2#include <aimdk_msgs/srv/play_media_file.hpp>
3#include <iostream>
4#include <rclcpp/rclcpp.hpp>
5#include <string>
6
7using PlayMediaFile = aimdk_msgs::srv::PlayMediaFile;
8
9int main(int argc, char **argv) {
10 rclcpp::init(argc, argv);
11 auto node = rclcpp::Node::make_shared("play_media_file_client_min");
12
13 // 1) Service name
14 const std::string service_name = "/aimdk_5Fmsgs/srv/PlayMediaFile";
15 auto client = node->create_client<PlayMediaFile>(service_name);
16
17 // 2) Input file path (prompt user if not provided as argument)
18 std::string default_file =
19 "/agibot/data/var/interaction/tts_cache/normal/demo.wav";
20 std::string file_name;
21
22 if (argc > 1) {
23 file_name = argv[1];
24 } else {
25 std::cout << "Enter the media file path to play (default: " << default_file
26 << "): ";
27 std::getline(std::cin, file_name);
28 if (file_name.empty()) {
29 file_name = default_file;
30 }
31 }
32
33 // 3) Build the request
34 auto req = std::make_shared<PlayMediaFile::Request>();
35 // CommonRequest request -> RequestHeader header -> builtin_interfaces/Time
36 // stamp
37 req->header.header.stamp = node->now();
38
39 // PlayMediaFileRequest required fields
40 req->media_file_req.file_name = file_name;
41 req->media_file_req.domain = "demo_client"; // Required: identifies the caller
42 req->media_file_req.trace_id = "demo"; // Optional: request identifier
43 req->media_file_req.is_interrupted =
44 true; // Whether to interrupt same-priority playback
45 req->media_file_req.priority_weight = 0; // Optional: 0~99
46 // Priority level: default INTERACTION_L6
47 req->media_file_req.priority_level.value = 6;
48
49 // 4) Wait for service and call
50 RCLCPP_INFO(node->get_logger(), "Waiting for service: %s",
51 service_name.c_str());
52 if (!client->wait_for_service(std::chrono::seconds(5))) {
53 RCLCPP_ERROR(node->get_logger(), "Service unavailable: %s",
54 service_name.c_str());
55 rclcpp::shutdown();
56 return 1;
57 }
58
59 auto future = client->async_send_request(req);
60 auto rc = rclcpp::spin_until_future_complete(node, future,
61 std::chrono::seconds(10));
62
63 if (rc == rclcpp::FutureReturnCode::INTERRUPTED) {
64 RCLCPP_WARN(node->get_logger(), "Interrupted while waiting");
65 rclcpp::shutdown();
66 return 1;
67 }
68
69 if (rc != rclcpp::FutureReturnCode::SUCCESS) {
70 RCLCPP_ERROR(node->get_logger(), "Call timed out or did not complete");
71 rclcpp::shutdown();
72 return 1;
73 }
74
75 // 5) Handle response (success is in tts_resp)
76 try {
77 const auto resp = future.get();
78 bool success = resp->tts_resp.is_success;
79
80 if (success) {
81 RCLCPP_INFO(node->get_logger(), "✅ Media file play request succeeded: %s",
82 file_name.c_str());
83 } else {
84 RCLCPP_ERROR(node->get_logger(), "❌ Media file play request failed: %s",
85 file_name.c_str());
86 }
87 } catch (const std::exception &e) {
88 RCLCPP_ERROR(node->get_logger(), "Call exception: %s", e.what());
89 }
90
91 rclcpp::shutdown();
92 return 0;
93}
使用说明:
# 播放默认音频文件
ros2 run examples play_media
# 播放指定音频文件
# 注意替换/path/to/your/audio_file.wav为交互板上实际文件路径
ros2 run examples play_media /path/to/your/audio_file.wav
# 播放TTS缓存文件
ros2 run examples play_media /agibot/data/var/interaction/tts_cache/normal/demo.wav
输出示例:
[INFO] [play_media_file_client_min]: ✅ 媒体文件播放请求成功
注意事项:
确保音频文件路径正确且文件存在
支持的文件格式:WAV、MP3等
优先级设置影响播放队列顺序
打断功能可中断当前播放的音频
6.2.17 TTS (文字转语音)
该示例中用到了play_tts,通过该节点可实现语音播放输入的文字,用户可根据不同的场景输入相应的文本。
功能特点:
支持命令行参数和交互式输入
完整的服务可用性检查和错误处理
支持优先级控制和打断机制
提供详细的播放状态反馈
核心代码
1#include <aimdk_msgs/msg/tts_priority_level.hpp>
2#include <aimdk_msgs/srv/play_tts.hpp>
3#include <iostream>
4#include <rclcpp/rclcpp.hpp>
5#include <string>
6
7using PlayTTS = aimdk_msgs::srv::PlayTts;
8
9int main(int argc, char **argv) {
10 rclcpp::init(argc, argv);
11 auto node = rclcpp::Node::make_shared("play_tts_client_min");
12
13 const std::string service_name = "/aimdk_5Fmsgs/srv/PlayTts";
14 auto client = node->create_client<PlayTTS>(service_name);
15
16 // Get text to speak
17 std::string tts_text;
18 if (argc > 1) {
19 tts_text = argv[1];
20 } else {
21 std::cout << "Enter text to speak: ";
22 std::getline(std::cin, tts_text);
23 if (tts_text.empty()) {
24 tts_text = "Hello, I am AgiBot X2.";
25 }
26 }
27
28 auto req = std::make_shared<PlayTTS::Request>();
29 req->header.header.stamp = node->now();
30 req->tts_req.text = tts_text;
31 req->tts_req.domain = "demo_client"; // Required: identifies the caller
32 req->tts_req.trace_id =
33 "demo"; // Optional: request identifier for the TTS request
34 req->tts_req.is_interrupted =
35 true; // Required: whether to interrupt same-priority playback
36 req->tts_req.priority_weight = 0;
37 req->tts_req.priority_level.value = 6;
38
39 if (!client->wait_for_service(
40 std::chrono::duration_cast<std::chrono::seconds>(
41 std::chrono::seconds(5)))) {
42 RCLCPP_ERROR(node->get_logger(), "Service unavailable: %s",
43 service_name.c_str());
44 rclcpp::shutdown();
45 return 1;
46 }
47
48 auto future = client->async_send_request(req);
49 if (rclcpp::spin_until_future_complete(
50 node, future,
51 std::chrono::duration_cast<std::chrono::seconds>(
52 std::chrono::seconds(10))) != rclcpp::FutureReturnCode::SUCCESS) {
53 RCLCPP_ERROR(node->get_logger(), "Call timed out");
54 rclcpp::shutdown();
55 return 1;
56 }
57
58 const auto resp = future.get();
59 if (resp->tts_resp.is_success) {
60 RCLCPP_INFO(node->get_logger(), "✅ TTS play request succeeded");
61 } else {
62 RCLCPP_ERROR(node->get_logger(), "❌ TTS play request failed");
63 }
64
65 rclcpp::shutdown();
66 return 0;
67}
使用说明
# 使用命令行参数播报文本(推荐)
ros2 run examples play_tts "你好,我是灵犀X2机器人"
# 或者不带参数运行,程序会提示用户输入
ros2 run examples play_tts
输出示例
[INFO] [play_tts_client_min]: ✅ 播报请求成功
注意事项
确保TTS服务正常运行
支持中文和英文文本播报
优先级设置影响播放队列顺序
打断功能可中断当前播放的语音
接口参考
服务:
/aimdk_5Fmsgs/srv/PlayTts消息:
aimdk_msgs/srv/PlayTts
6.2.18 麦克风数据接收
该示例中用到了mic_receiver,通过订阅/agent/process_audio_output话题来接收机器人的降噪音频数据,支持内置麦克风和外置麦克风两种音频流,并根据VAD(语音活动检测)状态自动保存完整的语音片段为PCM文件。
功能特点:
支持多音频流同时接收(内置麦克风 stream_id=1,外置麦克风 stream_id=2)
基于VAD状态自动检测语音开始、处理中、结束
自动保存完整语音片段为PCM格式文件
按时间戳和音频流分类存储
支持音频时长计算和统计信息输出
VAD状态说明:
0: 无语音1: 语音开始2: 语音处理中3: 语音结束
1#include <aimdk_msgs/msg/audio_vad_state_type.hpp>
2#include <aimdk_msgs/msg/processed_audio_output.hpp>
3#include <chrono>
4#include <ctime>
5#include <filesystem>
6#include <fstream>
7#include <iomanip>
8#include <rclcpp/rclcpp.hpp>
9#include <sstream>
10#include <string>
11#include <unordered_map>
12#include <vector>
13
14namespace fs = std::filesystem;
15
16class AudioSubscriber : public rclcpp::Node {
17public:
18 AudioSubscriber() : rclcpp::Node("audio_subscriber") {
19 // Audio buffers, stored separately by stream_id
20 // stream_id -> buffer
21 audio_buffers_ = {};
22 recording_state_ = {};
23
24 audio_output_dir_ = "audio_recordings";
25 fs::create_directories(audio_output_dir_);
26
27 // Note: deep queue to avoid missing data in a burst at start of VAD.
28 auto qos = rclcpp::QoS(
29 rclcpp::QoSInitialization::from_rmw(rmw_qos_profile_sensor_data));
30 qos.keep_last(500).best_effort();
31
32 subscription_ =
33 this->create_subscription<aimdk_msgs::msg::ProcessedAudioOutput>(
34 "/agent/process_audio_output", qos,
35 std::bind(&AudioSubscriber::audio_callback, this,
36 std::placeholders::_1));
37
38 RCLCPP_INFO(this->get_logger(),
39 "Starting to subscribe to denoised audio data...");
40 }
41
42private:
43 void
44 audio_callback(const aimdk_msgs::msg::ProcessedAudioOutput::SharedPtr msg) {
45 try {
46 uint32_t stream_id = msg->stream_id;
47 uint8_t vad_state = msg->audio_vad_state.value;
48 const std::vector<uint8_t> &audio_data = msg->audio_data;
49
50 static const std::unordered_map<uint8_t, std::string> vad_state_names = {
51 {0, "No Speech"},
52 {1, "Speech Start"},
53 {2, "Speech Processing"},
54 {3, "Speech End"}};
55 static const std::unordered_map<uint32_t, std::string> stream_names = {
56 {1, "Internal Microphone"}, {2, "External Microphone"}};
57
58 RCLCPP_INFO(this->get_logger(),
59 "Audio data received: stream_id=%u, vad_state=%u(%s), "
60 "audio_size=%zu bytes",
61 stream_id, vad_state,
62 vad_state_names.count(vad_state)
63 ? vad_state_names.at(vad_state).c_str()
64 : "Unknown State",
65 audio_data.size());
66
67 handle_vad_state(stream_id, vad_state, audio_data);
68 } catch (const std::exception &e) {
69 RCLCPP_ERROR(this->get_logger(), "Error processing audio message: %s",
70 e.what());
71 }
72 }
73
74 void handle_vad_state(uint32_t stream_id, uint8_t vad_state,
75 const std::vector<uint8_t> &audio_data) {
76 // Initialize the buffer for this stream_id (if it does not exist)
77 if (audio_buffers_.count(stream_id) == 0) {
78 audio_buffers_[stream_id] = std::vector<uint8_t>();
79 recording_state_[stream_id] = false;
80 }
81
82 static const std::unordered_map<uint8_t, std::string> vad_state_names = {
83 {0, "No Speech"},
84 {1, "Speech Start"},
85 {2, "Speech Processing"},
86 {3, "Speech End"}};
87 static const std::unordered_map<uint32_t, std::string> stream_names = {
88 {1, "Internal Microphone"}, {2, "External Microphone"}};
89
90 RCLCPP_INFO(this->get_logger(), "[%s] VAD Atate: %s Audio Data: %zu bytes",
91 stream_names.count(stream_id)
92 ? stream_names.at(stream_id).c_str()
93 : ("Unknown Stream " + std::to_string(stream_id)).c_str(),
94 vad_state_names.count(vad_state)
95 ? vad_state_names.at(vad_state).c_str()
96 : ("Unknown State" + std::to_string(vad_state)).c_str(),
97 audio_data.size());
98
99 // AUDIO_VAD_STATE_BEGIN
100 if (vad_state == 1) {
101 RCLCPP_INFO(this->get_logger(), "🎤 Speech detected - Start");
102 if (recording_state_[stream_id] == false) {
103 audio_buffers_[stream_id].clear();
104 recording_state_[stream_id] = true;
105 }
106 if (!audio_data.empty()) {
107 audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
108 audio_data.begin(), audio_data.end());
109 }
110
111 // AUDIO_VAD_STATE_PROCESSING
112 } else if (vad_state == 2) {
113 RCLCPP_INFO(this->get_logger(), "🔄 Speech Processing...");
114 if (recording_state_[stream_id] && !audio_data.empty()) {
115 audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
116 audio_data.begin(), audio_data.end());
117 }
118
119 // AUDIO_VAD_STATE_END
120 } else if (vad_state == 3) {
121 RCLCPP_INFO(this->get_logger(), "✅ Speech End");
122 if (recording_state_[stream_id] && !audio_data.empty()) {
123 audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
124 audio_data.begin(), audio_data.end());
125 }
126 if (recording_state_[stream_id] && !audio_buffers_[stream_id].empty()) {
127 save_audio_segment(audio_buffers_[stream_id], stream_id);
128 }
129 recording_state_[stream_id] = false;
130
131 // AUDIO_VAD_STATE_NONE
132 } else if (vad_state == 0) {
133 if (recording_state_[stream_id]) {
134 RCLCPP_INFO(this->get_logger(), "⏹️ Recording state reset");
135 recording_state_[stream_id] = false;
136 }
137 }
138
139 // Output the current buffer status.
140 size_t buffer_size = audio_buffers_[stream_id].size();
141 bool recording = recording_state_[stream_id];
142 RCLCPP_DEBUG(this->get_logger(),
143 "[Stream %u] Buffer size: %zu bytes, Recording state: %s",
144 stream_id, buffer_size, recording ? "true" : "false");
145 }
146
147 void save_audio_segment(const std::vector<uint8_t> &audio_data,
148 uint32_t stream_id) {
149 if (audio_data.empty())
150 return;
151
152 // Get the current timestamp.
153 auto now = std::chrono::system_clock::now();
154 std::time_t t = std::chrono::system_clock::to_time_t(now);
155 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
156 now.time_since_epoch()) %
157 1000;
158
159 std::ostringstream oss;
160 oss << std::put_time(std::localtime(&t), "%Y%m%d_%H%M%S") << "_"
161 << std::setw(3) << std::setfill('0') << ms.count();
162
163 // Create a subdirectory by stream_id.
164 fs::path stream_dir =
165 fs::path(audio_output_dir_) / ("stream_" + std::to_string(stream_id));
166 fs::create_directories(stream_dir);
167
168 static const std::unordered_map<uint32_t, std::string> stream_names = {
169 {1, "internal_mic"}, {2, "external_mic"}};
170 std::string stream_name = stream_names.count(stream_id)
171 ? stream_names.at(stream_id)
172 : ("stream_" + std::to_string(stream_id));
173 std::string filename = stream_name + "_" + oss.str() + ".pcm";
174 fs::path filepath = stream_dir / filename;
175
176 try {
177 std::ofstream ofs(filepath, std::ios::binary);
178 ofs.write(reinterpret_cast<const char *>(audio_data.data()),
179 audio_data.size());
180 ofs.close();
181 RCLCPP_INFO(this->get_logger(),
182 "Audio segment saved: %s (size: %zu bytes)", filepath.c_str(),
183 audio_data.size());
184
185 // Record audio file duration (assuming 16kHz, 16-bit, mono)
186 int sample_rate = 16000;
187 int bits_per_sample = 16;
188 int channels = 1;
189 int bytes_per_sample = bits_per_sample / 8;
190 size_t total_samples = audio_data.size() / (bytes_per_sample * channels);
191 double duration_seconds =
192 static_cast<double>(total_samples) / sample_rate;
193
194 RCLCPP_INFO(this->get_logger(),
195 "Audio duration: %.2f seconds (%zu samples)",
196 duration_seconds, total_samples);
197 } catch (const std::exception &e) {
198 RCLCPP_ERROR(this->get_logger(), "Failed to save audio file: %s",
199 e.what());
200 }
201 }
202
203 // Member variables
204 std::unordered_map<uint32_t, std::vector<uint8_t>> audio_buffers_;
205 std::unordered_map<uint32_t, bool> recording_state_;
206 std::string audio_output_dir_;
207 rclcpp::Subscription<aimdk_msgs::msg::ProcessedAudioOutput>::SharedPtr
208 subscription_;
209};
210
211int main(int argc, char **argv) {
212 rclcpp::init(argc, argv);
213 auto node = std::make_shared<AudioSubscriber>();
214 RCLCPP_INFO(node->get_logger(),
215 "Listening for denoised audio data, press Ctrl+C to exit...");
216 rclcpp::spin(node);
217 rclcpp::shutdown();
218 return 0;
219}
使用说明:
运行节点后会自动创建
audio_recordings目录音频文件按stream_id分类存储:
stream_1/: 内置麦克风音频stream_2/: 外置麦克风音频
文件命名格式:
{stream_name}_{timestamp}.pcm音频格式:16kHz, 16位, 单声道PCM
可通过以下命令播放保存的PCM文件:
aplay -r 16000 -f S16_LE -c 1 external_mic_20250909_133649_738.pcm
配合唤醒词触发VAD
输出示例:
[INFO] 开始订阅降噪音频数据...
[INFO] 收到音频数据: stream_id=2, vad_state=1(语音开始), audio_size=320 bytes
[INFO] [外置麦克风] VAD状态: 语音开始 音频数据: 320 bytes
[INFO] 🎤 检测到语音开始
[INFO] 收到音频数据: stream_id=2, vad_state=2(语音处理中), audio_size=320 bytes
[INFO] [外置麦克风] VAD状态: 语音处理中 音频数据: 320 bytes
[INFO] 🔄 语音处理中...
[INFO] 收到音频数据: stream_id=2, vad_state=3(语音结束), audio_size=320 bytes
[INFO] [外置麦克风] VAD状态: 语音结束 音频数据: 320 bytes
[INFO] ✅ 语音结束
[INFO] 音频段已保存: audio_recordings/stream_2/external_mic_20250909_133649_738.pcm (大小: 960 bytes)
[INFO] 音频时长: 0.06 秒 (480 样本)
播放PCM音频文件示例 (Linux下使用aplay命令)
假设你已经录制并保存了音频文件 external_mic_20250909_151117_223.pcm, 可以通过如下命令进行播放:
aplay -r 16000 -f S16_LE -c 1 audio_recordings/stream_2/external_mic_20250909_151117_223.pcm
参数说明:
-r 16000 # 采样率16kHz
-f S16_LE # 16位小端格式
-c 1 # 单声道 你也可以用其他音频播放器(如Audacity)以原始PCM格式导入并播放。
注意:如果你保存的是内置麦克风音频,路径应为 audio_recordings/stream_1/internal_mic_xxx.pcm
6.2.19 表情控制
该示例中用到了play_emoji,通过该节点可实现表达指定的表情,用户可根据已有的表情列表来选择要变化的表情,具体表情列表可参考表情列表
1#include "aimdk_msgs/srv/play_emoji.hpp"
2#include "aimdk_msgs/msg/common_request.hpp"
3#include "rclcpp/rclcpp.hpp"
4#include <chrono>
5#include <memory>
6#include <signal.h>
7#include <string>
8
9// Global variable used for signal handling
10std::shared_ptr<rclcpp::Node> g_node = nullptr;
11
12// Signal handler function
13void signal_handler(int signal) {
14 if (g_node) {
15 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
16 signal);
17 g_node.reset();
18 }
19 rclcpp::shutdown();
20 exit(signal);
21}
22
23class PlayEmojiClient : public rclcpp::Node {
24public:
25 PlayEmojiClient() : Node("play_emoji_client") {
26 client_ = this->create_client<aimdk_msgs::srv::PlayEmoji>(
27 "/face_ui_proxy/play_emoji");
28 RCLCPP_INFO(this->get_logger(), "✅ PlayEmoji client node started.");
29
30 // Wait for the service to become available
31 while (!client_->wait_for_service(std::chrono::seconds(2))) {
32 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
33 }
34 RCLCPP_INFO(this->get_logger(),
35 "🟢 Service available, ready to send request.");
36 }
37
38 bool send_request(uint8_t emoji, uint8_t mode, int32_t priority) {
39 try {
40 auto request = std::make_shared<aimdk_msgs::srv::PlayEmoji::Request>();
41
42 request->emotion_id = emoji;
43 request->mode = mode;
44 request->priority = priority;
45
46 RCLCPP_INFO(
47 this->get_logger(),
48 "📨 Sending request to play emoji: id=%hhu, mode=%hhu, priority=%d",
49 emoji, mode, priority);
50
51 const std::chrono::milliseconds timeout(250);
52 for (int i = 0; i < 8; i++) {
53 request->header.header.stamp = this->now();
54 auto future = client_->async_send_request(request);
55 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
56 future, timeout);
57 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
58 // retry as remote peer is NOT handled well by ROS
59 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
60 continue;
61 }
62 // future.done
63 auto response = future.get();
64 if (response->success) {
65 RCLCPP_INFO(this->get_logger(),
66 "✅ Request to play emoji recorded successfully: %s",
67 response->message.c_str());
68 return true;
69 } else {
70 RCLCPP_ERROR(this->get_logger(),
71 "❌ Failed to record play-emoji request: %s",
72 response->message.c_str());
73 return false;
74 }
75 }
76 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
77 return false;
78 } catch (const std::exception &e) {
79 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
80 return false;
81 }
82 }
83
84private:
85 rclcpp::Client<aimdk_msgs::srv::PlayEmoji>::SharedPtr client_;
86};
87
88int main(int argc, char **argv) {
89 try {
90 rclcpp::init(argc, argv);
91
92 // Set up signal handlers
93 signal(SIGINT, signal_handler);
94 signal(SIGTERM, signal_handler);
95
96 int32_t priority = 10;
97
98 int emotion = 1; // Expression type, 1 means Blink
99 std::cout
100 << "Enter expression ID: 1-Blink, 60-Bored, 70-Abnormal, 80-Sleeping, "
101 "90-Happy, 190-Very Angry, 200-Adoration"
102 << std::endl;
103 std::cin >> emotion;
104
105 int mode = 1; // Playback mode, 1 means play once, 2 means loop
106 std::cout << "Enter play mode (1: once, 2: loop): ";
107 std::cin >> mode;
108 if (mode < 1 || mode > 2) {
109 RCLCPP_ERROR(rclcpp::get_logger("main"), "Invalid play mode: %d", mode);
110 rclcpp::shutdown();
111 return 1;
112 }
113
114 g_node = std::make_shared<PlayEmojiClient>();
115 auto client = std::dynamic_pointer_cast<PlayEmojiClient>(g_node);
116
117 if (client) {
118 client->send_request(emotion, mode, priority);
119 }
120
121 // Clean up resources
122 g_node.reset();
123 rclcpp::shutdown();
124
125 return 0;
126 } catch (const std::exception &e) {
127 RCLCPP_ERROR(rclcpp::get_logger("main"),
128 "Program exited with exception: %s", e.what());
129 return 1;
130 }
131}
6.2.20 LED灯带控制
功能说明:演示如何控制机器人的LED灯带,支持多种显示模式和自定义颜色。
核心代码:
1#include <aimdk_msgs/msg/common_request.hpp>
2#include <aimdk_msgs/srv/led_strip_command.hpp>
3#include <chrono>
4#include <memory>
5#include <rclcpp/rclcpp.hpp>
6#include <signal.h>
7#include <string>
8
9std::shared_ptr<rclcpp::Node> g_node = nullptr;
10
11void signal_handler(int signal) {
12 if (g_node) {
13 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
14 signal);
15 g_node.reset();
16 }
17 rclcpp::shutdown();
18 exit(signal);
19}
20
21class PlayLightsClient : public rclcpp::Node {
22public:
23 PlayLightsClient() : Node("play_lights_client") {
24 client_ = this->create_client<aimdk_msgs::srv::LedStripCommand>(
25 "/aimdk_5Fmsgs/srv/LedStripCommand");
26 RCLCPP_INFO(this->get_logger(), "✅ PlayLights client node started.");
27
28 // Wait for the service to become available
29 while (!client_->wait_for_service(std::chrono::seconds(2))) {
30 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
31 }
32 RCLCPP_INFO(this->get_logger(),
33 "🟢 Service available, ready to send request.");
34 }
35
36 bool send_request(uint8_t led_mode, uint8_t r, uint8_t g, uint8_t b) {
37 try {
38 auto request =
39 std::make_shared<aimdk_msgs::srv::LedStripCommand::Request>();
40
41 request->led_strip_mode = led_mode;
42 request->r = r;
43 request->g = g;
44 request->b = b;
45
46 RCLCPP_INFO(this->get_logger(),
47 "📨 Sending request to control led strip: mode=%hhu, "
48 "RGB=(%hhu, %hhu, %hhu)",
49 led_mode, r, g, b);
50
51 // LED strip is slow to response (up to ~5s)
52 const std::chrono::milliseconds timeout(5000);
53 for (int i = 0; i < 4; i++) {
54 request->request.header.stamp = this->now();
55 auto future = client_->async_send_request(request);
56 auto retcode = rclcpp::spin_until_future_complete(
57 this->shared_from_this(), future, timeout);
58
59 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
60 // retry as remote peer is NOT handled well by ROS
61 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
62 continue;
63 }
64 // future.done
65 auto response = future.get();
66 if (response->status_code == 0) {
67 RCLCPP_INFO(this->get_logger(),
68 "✅ LED strip command sent successfully.");
69 return true;
70 } else {
71 RCLCPP_ERROR(this->get_logger(),
72 "❌ LED strip command failed with status: %d",
73 response->status_code);
74 return false;
75 }
76 }
77 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
78 return false;
79 } catch (const std::exception &e) {
80 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
81 return false;
82 }
83 }
84
85private:
86 rclcpp::Client<aimdk_msgs::srv::LedStripCommand>::SharedPtr client_;
87};
88
89int main(int argc, char **argv) {
90 try {
91 rclcpp::init(argc, argv);
92 signal(SIGINT, signal_handler);
93 signal(SIGTERM, signal_handler);
94
95 g_node = std::make_shared<PlayLightsClient>();
96 auto client_node = std::dynamic_pointer_cast<PlayLightsClient>(g_node);
97
98 int led_mode = 0; // LED Strip Mode
99 int r = 255, g = 0, b = 0; // RGB values
100
101 std::cout << "=== LED Strip Control Example ===" << std::endl;
102 std::cout << "Select LED strip mode:" << std::endl;
103 std::cout << "0 - Steady On" << std::endl;
104 std::cout << "1 - Breathing (4s period, sinusoidal brightness)"
105 << std::endl;
106 std::cout << "2 - Blinking (1s period, 0.5s on, 0.5s off)" << std::endl;
107 std::cout << "3 - Flow (2s period, lights turn on left to right)"
108 << std::endl;
109 std::cout << "Enter mode (0-3): ";
110 std::cin >> led_mode;
111
112 std::cout << "\nSet RGB color values (0-255):" << std::endl;
113 std::cout << "Red component (R): ";
114 std::cin >> r;
115 std::cout << "Green component (G): ";
116 std::cin >> g;
117 std::cout << "Blue component (B): ";
118 std::cin >> b;
119
120 // clamp mode to range 0-3
121 led_mode = std::max(0, std::min(3, led_mode));
122 // clamp r/g/b to range 0-255
123 r = std::max(0, std::min(255, r));
124 g = std::max(0, std::min(255, g));
125 b = std::max(0, std::min(255, b));
126
127 if (client_node) {
128 client_node->send_request(led_mode, r, g, b);
129 }
130
131 g_node.reset();
132 rclcpp::shutdown();
133
134 return 0;
135 } catch (const std::exception &e) {
136 RCLCPP_ERROR(rclcpp::get_logger("main"),
137 "Program terminated with exception: %s", e.what());
138 return 1;
139 }
140}
使用说明:
# 构建
colcon build --packages-select examples
# 运行
ros2 run examples play_lights
输出示例:
=== LED灯带控制示例 ===
请选择灯带模式:
0 - 常亮模式
1 - 呼吸模式 (4s周期,亮度正弦变化)
2 - 闪烁模式 (1s周期,0.5s亮,0.5s灭)
3 - 流水模式 (2s周期,从左到右依次点亮)
请输入模式 (0-3): 1
请设置RGB颜色值 (0-255):
红色分量 (R): 255
绿色分量 (G): 0
蓝色分量 (B): 0
发送LED控制命令...
模式: 1, 颜色: RGB(255, 0, 0)
✅ LED strip command sent successfully
技术特点:
支持4种LED显示模式
RGB颜色自定义
异步服务调用
输入参数验证
友好的用户交互界面