6.2 C++接口使用示例
本章节将带您实现索引所示若干功能
构建 & 运行说明
进入到 AimDK 顶层目录(机器人上为 ~/aimdk),执行以下命令
source /opt/ros/humble/setup.bash colcon build source install/local_setup.bash ros2 run examples '对应功能名称如: get_mc_action'
📝 代码说明
完整的代码实现包含完整的错误处理、信号处理、超时处理等机制,确保程序的健壮性。请您在 examples 目录下查看/修改
小心
ROS的服务(service)机制在跨板通信时存在一些待优化问题, 二次开发时请参考例程添加异常处理、快速重试等保护机制
机器人处于稳定站立 / 走跑模式时, 请勿批量快速启动 ROS 节点(建议不超过 2 个/秒), 短时间内大量节点参与 DDS 发现会造成通信拥塞、影响运控实时性, 存在机器人失稳摔倒风险
6.2.1 进入开发者模式
通过调用'GetSystemState'服务获取机器人当前的系统状态,并且通过'MigrateSystemState'进入对应的开发者模式。
1#include <algorithm>
2#include <chrono>
3#include <iostream>
4#include <memory>
5#include <string>
6
7#include "aimdk_msgs/srv/get_system_state.hpp"
8#include "aimdk_msgs/srv/migrate_system_state.hpp"
9#include "rclcpp/rclcpp.hpp"
10
11using namespace std::chrono_literals;
12
13/**
14 * @class SystemStateMigrator
15 * @brief ROS2 client for migrating system state to any target state
16 */
17class SystemStateMigrator : public rclcpp::Node {
18public:
19 /**
20 * @brief Constructor for SystemStateMigrator
21 * @param target_state The target system state to migrate to
22 */
23 SystemStateMigrator(const std::string &target_state)
24 : Node("system_state_migrator"), target_state_(target_state) {
25 // Create service clients
26 migrate_client_ = this->create_client<aimdk_msgs::srv::MigrateSystemState>(
27 "/aimdk_5Fmsgs/srv/MigrateSystemState");
28
29 get_state_client_ = this->create_client<aimdk_msgs::srv::GetSystemState>(
30 "/aimdk_5Fmsgs/srv/GetSystemState");
31
32 // Log target state
33 RCLCPP_INFO(this->get_logger(), "Target state: %s", target_state_.c_str());
34
35 // Start the migration process
36 start_migration_process();
37 }
38
39private:
40 /**
41 * @brief Start the migration process
42 */
43 void start_migration_process() {
44 // Create a timer to check service availability
45 check_timer_ = this->create_wall_timer(
46 1s, std::bind(&SystemStateMigrator::check_services_and_start, this));
47 }
48
49 /**
50 * @brief Check if services are available and start migration
51 */
52 void check_services_and_start() {
53 // Check if both services are available
54 if (migrate_client_->service_is_ready() &&
55 get_state_client_->service_is_ready()) {
56 // Services are available, cancel timer and start migration
57 check_timer_->cancel();
58 RCLCPP_INFO(this->get_logger(),
59 "Both services are available, starting migration to %s...",
60 target_state_.c_str());
61 migrate_to_target_state();
62 } else {
63 RCLCPP_INFO(this->get_logger(), "Waiting for services...");
64 }
65 }
66
67 /**
68 * @brief Migrate system state to target state with retry
69 */
70 void migrate_to_target_state() {
71 if (migrate_pending_) {
72 return;
73 }
74 if (migrate_retry_count_ >= max_migrate_retries_) {
75 RCLCPP_ERROR(this->get_logger(),
76 "Migration request failed after %d attempts",
77 max_migrate_retries_);
78 rclcpp::shutdown();
79 return;
80 }
81
82 auto request =
83 std::make_shared<aimdk_msgs::srv::MigrateSystemState::Request>();
84
85 auto now = this->now();
86 request->header.header.stamp.sec = now.seconds();
87 request->header.header.stamp.nanosec = now.nanoseconds() % 1000000000;
88
89 request->state = target_state_;
90
91 migrate_retry_count_++;
92 migrate_pending_ = true;
93 uint64_t req_id = ++migrate_request_id_;
94
95 RCLCPP_INFO(this->get_logger(),
96 "Sending migration request to %s state (attempt %d/%d)...",
97 target_state_.c_str(), migrate_retry_count_,
98 max_migrate_retries_);
99
100 migrate_timeout_timer_ = this->create_wall_timer(30s, [this, req_id]() {
101 if (req_id != migrate_request_id_) {
102 return;
103 }
104 migrate_timeout_timer_.reset();
105 migrate_pending_ = false;
106 RCLCPP_WARN(this->get_logger(), "Migration request timeout, retrying...");
107 migrate_to_target_state();
108 });
109
110 auto future = migrate_client_->async_send_request(
111 request,
112 [this, req_id](
113 rclcpp::Client<aimdk_msgs::srv::MigrateSystemState>::SharedFuture
114 future) {
115 if (req_id != migrate_request_id_) {
116 return;
117 }
118 if (migrate_timeout_timer_) {
119 migrate_timeout_timer_->cancel();
120 migrate_timeout_timer_.reset();
121 }
122 migrate_pending_ = false;
123
124 try {
125 auto response = future.get();
126 if (response->header.status.value ==
127 aimdk_msgs::msg::CommonState::SUCCESS) { // SUCCESS (1)
128 migrate_retry_count_ = 0;
129 ++migrate_request_id_;
130 RCLCPP_INFO(this->get_logger(),
131 "Migration request accepted, starting to monitor "
132 "state...");
133 start_state_monitoring();
134 } else {
135 ++migrate_request_id_;
136 RCLCPP_ERROR(this->get_logger(),
137 "Migration request failed with status: %d, message: "
138 "%s",
139 response->header.status.value,
140 response->header.message.c_str());
141 rclcpp::shutdown();
142 }
143 } catch (const std::exception &e) {
144 ++migrate_request_id_;
145 RCLCPP_ERROR(this->get_logger(),
146 "Migration service call failed: %s", e.what());
147 rclcpp::shutdown();
148 }
149 });
150 }
151
152 /**
153 * @brief Start monitoring system state
154 */
155 void start_state_monitoring() {
156 RCLCPP_INFO(this->get_logger(), "Starting to monitor system state...");
157
158 // Create a timer to periodically check system state
159 monitor_timer_ = this->create_wall_timer(
160 1s, std::bind(&SystemStateMigrator::check_system_state, this));
161 }
162
163 /**
164 * @brief Check current system state with retry
165 */
166 void check_system_state() {
167 if (get_state_pending_) {
168 return;
169 }
170 if (get_state_retry_count_ >= max_get_state_retries_) {
171 RCLCPP_ERROR(this->get_logger(),
172 "Get system state failed after %d attempts, will retry on "
173 "next monitor cycle",
174 max_get_state_retries_);
175 get_state_retry_count_ = 0;
176 return;
177 }
178
179 auto request = std::make_shared<aimdk_msgs::srv::GetSystemState::Request>();
180
181 auto now = this->now();
182 request->header.header.stamp.sec = now.seconds();
183 request->header.header.stamp.nanosec = now.nanoseconds() % 1000000000;
184
185 get_state_retry_count_++;
186 get_state_pending_ = true;
187 uint64_t req_id = ++get_state_request_id_;
188
189 get_state_timeout_timer_ = this->create_wall_timer(10s, [this, req_id]() {
190 if (req_id != get_state_request_id_) {
191 return;
192 }
193 get_state_timeout_timer_.reset();
194 get_state_pending_ = false;
195 RCLCPP_WARN(this->get_logger(),
196 "GetSystemState request timeout (attempt %d/%d), "
197 "retrying...",
198 get_state_retry_count_, max_get_state_retries_);
199 check_system_state();
200 });
201
202 auto future = get_state_client_->async_send_request(
203 request,
204 [this,
205 req_id](rclcpp::Client<aimdk_msgs::srv::GetSystemState>::SharedFuture
206 future) {
207 if (req_id != get_state_request_id_) {
208 return;
209 }
210 if (get_state_timeout_timer_) {
211 get_state_timeout_timer_->cancel();
212 get_state_timeout_timer_.reset();
213 }
214 get_state_pending_ = false;
215
216 try {
217 auto response = future.get();
218 get_state_retry_count_ = 0;
219 ++get_state_request_id_;
220
221 RCLCPP_INFO(
222 this->get_logger(), "Current State: %s, System Status: %u",
223 response->cur_state.c_str(), response->curr_status.value);
224
225 if (is_migration_complete(response)) {
226 RCLCPP_INFO(this->get_logger(),
227 "Migration to %s completed successfully!",
228 target_state_.c_str());
229 monitor_timer_->cancel();
230 rclcpp::shutdown();
231 } else {
232 RCLCPP_INFO(this->get_logger(),
233 "Migration in progress, will check again in 1 "
234 "second...");
235 }
236 } catch (const std::exception &e) {
237 RCLCPP_ERROR(this->get_logger(),
238 "GetSystemState service call failed: %s", e.what());
239 }
240 });
241 }
242
243 /**
244 * @brief Check if migration to target state is complete
245 * @param response Response from GetSystemState service
246 * @return true if migration is complete, false otherwise
247 */
248 bool is_migration_complete(
249 const aimdk_msgs::srv::GetSystemState::Response::SharedPtr response) {
250 // Convert current state and target state to lowercase for case-insensitive
251 // comparison
252 std::string current_state_lower = response->cur_state;
253 std::transform(current_state_lower.begin(), current_state_lower.end(),
254 current_state_lower.begin(), ::tolower);
255
256 std::string target_state_lower = target_state_;
257 std::transform(target_state_lower.begin(), target_state_lower.end(),
258 target_state_lower.begin(), ::tolower);
259
260 // Check conditions:
261 // 1. Current state equals target state (case-insensitive)
262 // Special case: Ready auto-transitions to Business, so also accept
263 // Business when the target is Ready
264 // 2. System status equals 1 (IN_READY), or the target is Ready and
265 // the system has already auto-transitioned to Business (IN_MOVE)
266 bool state_match =
267 (current_state_lower == target_state_lower) ||
268 (target_state_lower == "ready" && current_state_lower == "business");
269 bool ready_auto_transitioned =
270 (target_state_lower == "ready" && current_state_lower == "business");
271 bool status_match = ready_auto_transitioned ||
272 (response->curr_status.value ==
273 aimdk_msgs::msg::SystemStatus::IN_READY); // READY (1)
274
275 if (state_match && status_match) {
276 return true;
277 }
278
279 // Log detailed information if not complete
280 if (!state_match) {
281 RCLCPP_INFO(this->get_logger(),
282 "State mismatch: current='%s', expected='%s'",
283 response->cur_state.c_str(), target_state_.c_str());
284 }
285 if (!status_match) {
286 RCLCPP_INFO(this->get_logger(),
287 "Status mismatch: current= %u, expected=1 (IN_READY)",
288 response->curr_status.value);
289 }
290 RCLCPP_ERROR(this->get_logger(), "State Migrate Fail");
291
292 return false;
293 }
294
295 // Target state to migrate to
296 std::string target_state_;
297
298 // Service clients
299 rclcpp::Client<aimdk_msgs::srv::MigrateSystemState>::SharedPtr
300 migrate_client_;
301 rclcpp::Client<aimdk_msgs::srv::GetSystemState>::SharedPtr get_state_client_;
302
303 // Timers
304 rclcpp::TimerBase::SharedPtr check_timer_;
305 rclcpp::TimerBase::SharedPtr monitor_timer_;
306
307 // Retry counters and timeout timers for migration
308 int migrate_retry_count_ = 0;
309 const int max_migrate_retries_ = 5;
310 uint64_t migrate_request_id_ = 0;
311 bool migrate_pending_ = false;
312 rclcpp::TimerBase::SharedPtr migrate_timeout_timer_;
313
314 // Retry counters and timeout timers for get state
315 int get_state_retry_count_ = 0;
316 const int max_get_state_retries_ = 3;
317 uint64_t get_state_request_id_ = 0;
318 bool get_state_pending_ = false;
319 rclcpp::TimerBase::SharedPtr get_state_timeout_timer_;
320};
321
322/**
323 * @brief Print usage information with detailed mode descriptions
324 */
325void print_usage(const char *program_name) {
326 std::cout << "Usage: " << program_name << " <target_state>" << std::endl;
327 std::cout << std::endl;
328 std::cout << "Available target states:" << std::endl;
329 std::cout << " " << program_name << " Ready" << std::endl;
330 std::cout << " " << program_name << " Develop_Nav" << std::endl;
331 std::cout << " " << program_name << " Develop_Audio_Linux" << std::endl;
332 std::cout << " " << program_name << " Develop_Audio_ROS" << std::endl;
333 std::cout << " " << program_name << " Develop_MC" << std::endl;
334 std::cout << std::endl;
335
336 std::cout << "Mode Descriptions:" << std::endl;
337 std::cout << "==================" << std::endl;
338
339 // Ready mode description
340 std::cout << "1. Ready - System Default Mode" << std::endl;
341 std::cout
342 << " • Description: System enters this mode by default after startup"
343 << std::endl;
344 std::cout << " • Purpose: Normal operation mode, ready for general tasks"
345 << std::endl;
346 std::cout
347 << " • Use case: Return to normal operation after development work"
348 << std::endl;
349 std::cout << std::endl;
350
351 // Develop_Nav mode description
352 std::cout << "2. Develop_Nav - Navigation Development Mode" << std::endl;
353 std::cout
354 << " • Description: Special mode for navigation system development"
355 << std::endl;
356 std::cout << " • Purpose: Enables navigation-related debugging and testing"
357 << std::endl;
358 std::cout << " • Use case: When developing or testing navigation "
359 "algorithms, SLAM, path planning"
360 << std::endl;
361 std::cout << " • Features: May provide access to raw sensor data, "
362 "navigation debug topics"
363 << std::endl;
364 std::cout << std::endl;
365
366 // Develop_Audio_Linux mode description
367 std::cout << "3. Develop_Audio_Linux - System-Level Audio Development Mode"
368 << std::endl;
369 std::cout << " • Description: Mode for low-level audio system development"
370 << std::endl;
371 std::cout << " • Purpose: Access to system audio streams at Linux level"
372 << std::endl;
373 std::cout << " • Use case: When developing audio drivers, audio processing "
374 "at system level"
375 << std::endl;
376 std::cout << " • Features: Direct access to audio hardware, "
377 "ALSA/PulseAudio interfaces"
378 << std::endl;
379 std::cout << std::endl;
380
381 // Develop_Audio_ROS mode description
382 std::cout << "4. Develop_Audio_ROS - ROS Audio Development Mode" << std::endl;
383 std::cout
384 << " • Description: Mode for ROS-based audio application development"
385 << std::endl;
386 std::cout << " • Purpose: Access to audio data through ROS topics"
387 << std::endl;
388 std::cout << " • Use case: When developing ROS nodes that process audio, "
389 "speech recognition"
390 << std::endl;
391 std::cout << " • Features: Audio data published as ROS topics, ROS message "
392 "interfaces"
393 << std::endl;
394 std::cout << std::endl;
395
396 // Develop_MC mode description
397 std::cout << "5. Develop_MC - Motion Control Development Mode" << std::endl;
398 std::cout << " • Description: Mode for motion control system development"
399 << std::endl;
400 std::cout
401 << " • Purpose: Enables direct control and testing of motion systems"
402 << std::endl;
403 std::cout << " • Use case: When developing motor control algorithms, "
404 "testing motion hardware"
405 << std::endl;
406 std::cout << " • Features: Low-level motor control access, motion system "
407 "debugging tools"
408 << std::endl;
409 std::cout << std::endl;
410
411 std::cout << "Program Workflow:" << std::endl;
412 std::cout << "=================" << std::endl;
413 std::cout
414 << "1. Calls MigrateSystemState service with the specified target state"
415 << std::endl;
416 std::cout << "2. Continuously monitors GetSystemState service every second"
417 << std::endl;
418 std::cout << "3. Exits when both conditions are met:" << std::endl;
419 std::cout << " • Current State equals target state (case-insensitive)"
420 << std::endl;
421 std::cout << " • System Status equals 1 (IN_READY)" << std::endl;
422 std::cout << std::endl;
423 std::cout << "Note: The program will automatically exit when migration is "
424 "complete or on error."
425 << std::endl;
426}
427
428/**
429 * @brief Validate if the target state is a known development mode
430 * @param state The state to validate
431 * @return true if state is valid, false otherwise
432 */
433bool is_valid_state(const std::string &state) {
434 // List of known valid states
435 const std::vector<std::string> valid_states = {
436 "Ready", "Develop_Nav", "Develop_Audio_Linux", "Develop_Audio_ROS",
437 "Develop_MC"};
438
439 // Convert input to lowercase for case-insensitive comparison
440 std::string state_lower = state;
441 std::transform(state_lower.begin(), state_lower.end(), state_lower.begin(),
442 ::tolower);
443
444 // Check if state is in the list of valid states
445 for (const auto &valid_state : valid_states) {
446 std::string valid_state_lower = valid_state;
447 std::transform(valid_state_lower.begin(), valid_state_lower.end(),
448 valid_state_lower.begin(), ::tolower);
449
450 if (state_lower == valid_state_lower) {
451 return true;
452 }
453 }
454
455 return false;
456}
457
458/**
459 * @brief Main function
460 * @param argc Argument count
461 * @param argv Argument vector
462 * @return Exit code
463 */
464int main(int argc, char **argv) {
465 // Check command line arguments
466 if (argc != 2) {
467 print_usage(argv[0]);
468 return 1;
469 }
470
471 // Get target state from command line argument
472 std::string target_state = argv[1];
473
474 // Validate target state
475 if (target_state.empty()) {
476 std::cerr << "Error: Target state cannot be empty" << std::endl;
477 print_usage(argv[0]);
478 return 1;
479 }
480
481 // Optional: Validate against known states
482 if (!is_valid_state(target_state)) {
483 std::cout << "Warning: '" << target_state
484 << "' is not in the list of known states." << std::endl;
485 std::cout << "The program will still attempt to migrate to this state."
486 << std::endl;
487 std::cout << "Continue? (y/n): ";
488
489 std::string response;
490 std::getline(std::cin, response);
491
492 if (response != "y" && response != "Y") {
493 std::cout << "Operation cancelled." << std::endl;
494 return 0;
495 }
496 }
497
498 // Initialize ROS2
499 rclcpp::init(argc, argv);
500
501 // Create migrator node with target state
502 auto node = std::make_shared<SystemStateMigrator>(target_state);
503
504 // Create single-threaded executor
505 auto executor = std::make_shared<rclcpp::executors::SingleThreadedExecutor>();
506
507 // Add node to executor
508 executor->add_node(node);
509
510 // Run executor (blocking call)
511 executor->spin();
512
513 // Cleanup
514 executor->remove_node(node);
515 rclcpp::shutdown();
516
517 return 0;
518}
使用说明
ros2 run examples migrate_system_state Develop_MC
输出示例
[INFO] [1770125761.202173770] [system_state_migrator]: Target state: Develop_MC
[INFO] [1770125762.202825297] [system_state_migrator]: Both services are available, starting migration to Develop_MC...
[INFO] [1770125762.202949414] [system_state_migrator]: Sending migration request to Develop_MC state (attempt 1/5)...
[INFO] [1770125762.264580770] [system_state_migrator]: Migration request accepted, starting to monitor state...
[INFO] [1770125762.264608479] [system_state_migrator]: Starting to monitor system state...
[INFO] [1770125763.268422431] [system_state_migrator]: Current State: Business, System Status: 2
[INFO] [1770125763.268513271] [system_state_migrator]: State mismatch: current='Business', expected='Develop_MC'
[INFO] [1770125763.268531394] [system_state_migrator]: Status mismatch: current= 2, expected=1 (IN_READY)
[INFO] [1770125763.268545617] [system_state_migrator]: Migration in progress, will check again in 1 second...
[INFO] [1770125764.266388052] [system_state_migrator]: Current State: Business, System Status: 2
[INFO] [1770125764.266492040] [system_state_migrator]: State mismatch: current='Business', expected='Develop_MC'
[INFO] [1770125764.266509640] [system_state_migrator]: Status mismatch: current= 2, expected=1 (IN_READY)
[INFO] [1770125764.266532986] [system_state_migrator]: Migration in progress, will check again in 1 second...
[INFO] [1770125765.266452308] [system_state_migrator]: Current State: Develop_MC, System Status: 1
[INFO] [1770125765.266562888] [system_state_migrator]: Migration to Develop_MC completed successfully!
6.2.2 获取机器人模式
通过调用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] [1779173975.378396584] [get_mc_action_client]: ✅ GetMcAction client node created.
[INFO] [1779173975.961720731] [get_mc_action_client]: 🟢 Service available, ready to send request.
[INFO] [1779173975.961868725] [get_mc_action_client]: 📨 Sending request to get robot mode
[INFO] [1779173975.965180630] [get_mc_action_client]: ✅ Robot mode get successfully.
[INFO] [1779173975.965282450] [get_mc_action_client]: Mode name: PASSIVE_DEFAULT
[INFO] [1779173975.965307345] [get_mc_action_client]: Mode status: 100
接口参考
服务:
/aimdk_5Fmsgs/srv/GetMcAction消息:
aimdk_msgs/srv/GetMcAction
6.2.3 设置机器人模式
该示例中用到了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 <iostream>
11#include <memory>
12#include <signal.h>
13#include <unordered_map>
14#include <vector>
15
16// Global variable used for signal handling
17std::shared_ptr<rclcpp::Node> g_node = nullptr;
18
19// Signal handler function
20void signal_handler(int signal) {
21 if (g_node) {
22 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
23 signal);
24 g_node.reset();
25 }
26 rclcpp::shutdown();
27 exit(signal);
28}
29
30class SetMcActionClient : public rclcpp::Node {
31public:
32 SetMcActionClient() : Node("set_mc_action_client") {
33
34 client_ = this->create_client<aimdk_msgs::srv::SetMcAction>(
35 "/aimdk_5Fmsgs/srv/SetMcAction");
36 RCLCPP_INFO(this->get_logger(), "✅ SetMcAction client node created.");
37
38 // Wait for the service to become available
39 while (!client_->wait_for_service(std::chrono::seconds(2))) {
40 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
41 }
42 RCLCPP_INFO(this->get_logger(),
43 "🟢 Service available, ready to send request.");
44 }
45
46 bool send_request(std::string &action_name) {
47 try {
48 auto request = std::make_shared<aimdk_msgs::srv::SetMcAction::Request>();
49 request->header = aimdk_msgs::msg::RequestHeader();
50
51 request->source = "node.set_mc_action";
52
53 // Set robot mode
54 aimdk_msgs::msg::McActionCommand command;
55 command.action_desc = action_name;
56 request->command = command;
57
58 RCLCPP_INFO(this->get_logger(), "📨 Sending request to set robot mode: %s",
59 action_name.c_str());
60
61 // Set Service Call Timeout
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.status.value ==
76 aimdk_msgs::msg::CommonState::SUCCESS) { // SUCCESS (1)
77 RCLCPP_INFO(this->get_logger(), "✅ Robot mode set successfully.");
78 return true;
79 } else {
80 RCLCPP_ERROR(this->get_logger(), "❌ Failed (code=%ld): %s",
81 static_cast<long>(response->response.header.code),
82 response->response.message.c_str());
83 return false;
84 }
85 }
86 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
87 return false;
88 } catch (const std::exception &e) {
89 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
90 return false;
91 }
92 }
93
94private:
95 rclcpp::Client<aimdk_msgs::srv::SetMcAction>::SharedPtr client_;
96};
97
98static std::unordered_map<std::string, std::vector<std::string>> g_action_info =
99 {
100 {"PASSIVE_DEFAULT", {"PD", "joints with zero torque"}},
101 {"DAMPING_DEFAULT", {"DD", "joints in damping mode"}},
102 {"JOINT_DEFAULT", {"JD", "Position Control Stand (joints locked)"}},
103 {"STAND_DEFAULT", {"SD", "Stable Stand (auto-balance)"}},
104 {"LOCOMOTION_DEFAULT", {"LD", "locomotion mode (walk or run)"}},
105 {"HEAD_ONLY", {"HO", "head-only control"}},
106 {"UPPERBODY_REMOTE_SPLIT",
107 {"URS", "upper body remote split (head + arms + hands)"}},
108};
109
110int main(int argc, char *argv[]) {
111 try {
112 rclcpp::init(argc, argv);
113
114 // Set up signal handlers
115 signal(SIGINT, signal_handler);
116 signal(SIGTERM, signal_handler);
117
118 // Create node
119 g_node = std::make_shared<SetMcActionClient>();
120 auto client = std::dynamic_pointer_cast<SetMcActionClient>(g_node);
121
122 if (client) {
123 std::unordered_map<std::string, std::string> choices;
124 std::string motion;
125
126 // Prefer command-line argument; otherwise prompt user
127 if (argc > 1) {
128 motion = argv[1];
129 RCLCPP_INFO(g_node->get_logger(),
130 "Using abbr of motion mode from cmdline: %s", argv[1]);
131 } else {
132 std::cout << std::left << std::setw(4) << "abbr"
133 << " - " << std::setw(20) << "robot mode"
134 << " : "
135 << "description" << std::endl;
136 for (auto &it : g_action_info) {
137 std::cout << std::left << std::setw(4) << it.second[0] << " - "
138 << std::setw(20) << it.first << " : " << it.second[1]
139 << std::endl;
140 }
141 std::cout << "Enter abbr of motion mode:";
142 std::cin >> motion;
143 }
144 for (auto &it : g_action_info) {
145 choices[it.second[0]] = it.first;
146 }
147
148 auto m = choices.find(motion);
149 if (m != choices.end()) {
150 auto &action_name = m->second;
151 client->send_request(action_name);
152 } else {
153 RCLCPP_ERROR(g_node->get_logger(), "Invalid abbr of robot mode: %s",
154 motion.c_str());
155 }
156 }
157
158 // Clean up resources
159 g_node.reset();
160 rclcpp::shutdown();
161
162 return 0;
163 } catch (const std::exception &e) {
164 RCLCPP_ERROR(rclcpp::get_logger("main"),
165 "Program exited with exception: %s", e.what());
166 return 1;
167 }
168}
使用说明
# 使用命令行参数设置模式(推荐)
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.4 设置机器人动作
该示例中用到了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_input_action.hpp"
6#include "aimdk_msgs/msg/mc_preset_motion.hpp"
7#include "aimdk_msgs/msg/request_header.hpp"
8#include "aimdk_msgs/srv/set_mc_input_source.hpp"
9#include "aimdk_msgs/srv/set_mc_preset_motion.hpp"
10#include "rclcpp/rclcpp.hpp"
11#include <chrono>
12#include <cinttypes>
13#include <iostream>
14#include <memory>
15#include <signal.h>
16
17namespace {
18constexpr const char *kInputSourceName = "node.preset_motion";
19constexpr int32_t kInputSourcePriority = 40;
20constexpr int32_t kInputSourceTimeout = 1000;
21} // namespace
22
23std::shared_ptr<rclcpp::Node> g_node = nullptr;
24
25void signal_handler(int signal) {
26 if (g_node) {
27 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
28 signal);
29 g_node.reset();
30 }
31 rclcpp::shutdown();
32 exit(signal);
33}
34
35class PresetMotionClient : public rclcpp::Node {
36public:
37 PresetMotionClient() : Node("preset_motion_client") {
38 client_ = this->create_client<aimdk_msgs::srv::SetMcPresetMotion>(
39 "/aimdk_5Fmsgs/srv/SetMcPresetMotion");
40 input_source_client_ =
41 this->create_client<aimdk_msgs::srv::SetMcInputSource>(
42 "/aimdk_5Fmsgs/srv/SetMcInputSource");
43
44 RCLCPP_INFO(this->get_logger(), "✅ SetMcPresetMotion client node created.");
45
46 // Wait for the service to become available
47 while (!client_->wait_for_service(std::chrono::seconds(2))) {
48 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
49 }
50 RCLCPP_INFO(this->get_logger(),
51 "🟢 Service available, ready to send request.");
52 }
53
54 bool register_input_source() {
55 if (!input_source_client_->wait_for_service(std::chrono::seconds(8))) {
56 RCLCPP_ERROR(this->get_logger(),
57 "❌ SetMcInputSource service unavailable.");
58 return false;
59 }
60
61 // Try ADD first; if it fails (likely already registered), fall back to
62 // ENABLE
63 if (call_input_source( // ADD (1001)
64 aimdk_msgs::msg::McInputAction::INPUTACTION_ADD)) {
65 RCLCPP_INFO(this->get_logger(), "✅ Input source \"%s\" registered (ADD).",
66 kInputSourceName);
67 return true;
68 }
69 RCLCPP_INFO(this->get_logger(),
70 "ADD failed (likely already registered), trying ENABLE...");
71 if (call_input_source( // ENABLE (2001)
72 aimdk_msgs::msg::McInputAction::INPUTACTION_ENABLE)) {
73 RCLCPP_INFO(this->get_logger(), "✅ Input source \"%s\" enabled.",
74 kInputSourceName);
75 return true;
76 }
77 RCLCPP_ERROR(this->get_logger(),
78 "❌ Failed to register input source via ADD or ENABLE.");
79 return false;
80 }
81
82 bool send_request(int area_id, int motion_id) {
83 try {
84 auto request =
85 std::make_shared<aimdk_msgs::srv::SetMcPresetMotion::Request>();
86 request->header = aimdk_msgs::msg::RequestHeader();
87
88 request->input_source.name = kInputSourceName;
89 request->input_source.priority = kInputSourcePriority;
90 request->input_source.timeout = kInputSourceTimeout;
91
92 aimdk_msgs::msg::McPresetMotion motion;
93 aimdk_msgs::msg::McControlArea area;
94
95 motion.value = motion_id; // Preset motion ID
96 area.value = area_id; // Control area ID
97 request->motion = motion;
98 request->area = area;
99 request->interrupt = false; // Not interrupt current motion
100
101 RCLCPP_INFO(this->get_logger(),
102 "📨 Sending request to set preset motion: motion=%d, area=%d",
103 motion_id, area_id);
104
105 const std::chrono::milliseconds timeout(250);
106 for (int i = 0; i < 8; i++) {
107 request->header.stamp = this->now();
108 auto future = client_->async_send_request(request);
109 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
110 future, timeout);
111 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
112 // retry as remote peer is NOT handled well by ROS
113 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
114 continue;
115 }
116 // future.done
117 auto response = future.get();
118 if (response->response.header.code == 0) {
119 RCLCPP_INFO(this->get_logger(),
120 "✅ Preset motion set successfully: %" PRIu64,
121 response->response.task_id);
122 return true;
123 } else if (response->response.state.value ==
124 aimdk_msgs::msg::CommonState::RUNNING) { // RUNNING (400)
125 RCLCPP_INFO(this->get_logger(), "⏳ Preset motion executing: %" PRIu64,
126 response->response.task_id);
127 return true;
128 } else {
129 RCLCPP_WARN(this->get_logger(),
130 "❌ Failed to set preset motion: %" PRIu64,
131 response->response.task_id);
132 return false;
133 }
134 }
135 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
136 return false;
137 } catch (const std::exception &e) {
138 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
139 return false;
140 }
141 }
142
143private:
144 bool call_input_source(int32_t action_value) {
145 auto req = std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
146 req->action.value = action_value;
147 req->input_source.name = kInputSourceName;
148 req->input_source.priority = kInputSourcePriority;
149 req->input_source.timeout = kInputSourceTimeout;
150
151 const std::chrono::milliseconds timeout(250);
152 for (int i = 0; i < 8; i++) {
153 req->request.header.stamp = this->now();
154 auto future = input_source_client_->async_send_request(req);
155 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
156 future, timeout);
157 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
158 // retry as remote peer is NOT handled well by ROS
159 RCLCPP_INFO(
160 this->get_logger(), "trying input source action %s (%d)... [%d]",
161 action_value == aimdk_msgs::msg::McInputAction::INPUTACTION_ADD
162 ? "ADD"
163 : "ENABLE",
164 action_value, i);
165 continue;
166 }
167 auto response = future.get();
168 return response->response.header.code == 0;
169 }
170 return false;
171 }
172
173 rclcpp::Client<aimdk_msgs::srv::SetMcPresetMotion>::SharedPtr client_;
174 rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr
175 input_source_client_;
176};
177
178int main(int argc, char *argv[]) {
179 try {
180 rclcpp::init(argc, argv);
181 signal(SIGINT, signal_handler);
182 signal(SIGTERM, signal_handler);
183
184 g_node = std::make_shared<PresetMotionClient>();
185 // Cast g_node (std::shared_ptr<rclcpp::Node>) to a derived
186 // PresetMotionClient pointer (std::shared_ptr<PresetMotionClient>)
187 auto client = std::dynamic_pointer_cast<PresetMotionClient>(g_node);
188
189 int area = 1;
190 int motion = 1003;
191 std::cout << "Enter arm area ID (1-left, 2-right): ";
192 std::cin >> area;
193 std::cout
194 << "Enter preset motion ID (1001-raise, 1002-wave, 1003-handshake, "
195 "1004-airkiss): ";
196 std::cin >> motion;
197 if (client) {
198 if (!client->register_input_source()) {
199 RCLCPP_ERROR(g_node->get_logger(),
200 "❌ Input source registration failed, exiting");
201 } else {
202 client->send_request(area, motion);
203 }
204 }
205
206 // Clean up resources
207 g_node.reset();
208 rclcpp::shutdown();
209
210 return 0;
211 } catch (const std::exception &e) {
212 RCLCPP_ERROR(rclcpp::get_logger("main"),
213 "Program exited with exception: %s", e.what());
214 return 1;
215 }
216}
6.2.5 夹爪控制
该示例中用到了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 <memory>
8#include <vector>
9
10class HandControl : public rclcpp::Node {
11public:
12 HandControl()
13 : Node("hand_control"), position_pairs_({
14 {1.0, 1.0},
15 {0.0, 0.0},
16 {0.5, 0.5},
17 {0.2, 0.8},
18 {0.7, 0.3},
19 }),
20 current_index_(0) {
21 publisher_ = this->create_publisher<aimdk_msgs::msg::HandCommandArray>(
22 "/aima/hal/joint/hand/command", 10);
23
24 timer_ = this->create_wall_timer(
25 std::chrono::milliseconds(20), // 50Hz
26 std::bind(&HandControl::publish_hand_commands, this));
27
28 last_switch_time_ = now();
29 RCLCPP_INFO(this->get_logger(), "The hand control node has been started!");
30 }
31
32 void publish_hand_commands() {
33 // 1. Determine if it's time to switch parameters.
34 auto now_time = this->now();
35 if ((now_time - last_switch_time_).seconds() >= 2.0) {
36 current_index_ = (current_index_ + 1) % position_pairs_.size();
37 last_switch_time_ = now_time;
38 RCLCPP_INFO(this->get_logger(),
39 "Switched to the next parameter group, index=%zu (left=%.2f, "
40 "right=%.2f)",
41 current_index_, position_pairs_[current_index_].first,
42 position_pairs_[current_index_].second);
43 }
44
45 auto msg = std::make_unique<aimdk_msgs::msg::HandCommandArray>();
46 msg->header = aimdk_msgs::msg::MessageHeader();
47
48 float left_position = position_pairs_[current_index_].first;
49 float right_position = position_pairs_[current_index_].second;
50
51 aimdk_msgs::msg::HandCommand left_hands;
52 left_hands.name = "left_hand";
53 left_hands.position = left_position;
54 left_hands.velocity = 1.0;
55 left_hands.acceleration = 1.0;
56 left_hands.deceleration = 1.0;
57 left_hands.effort = 1.0;
58
59 aimdk_msgs::msg::HandCommand right_hands;
60 right_hands.name = "right_hand";
61 right_hands.position = right_position;
62 right_hands.velocity = 1.0;
63 right_hands.acceleration = 1.0;
64 right_hands.deceleration = 1.0;
65 right_hands.effort = 1.0;
66
67 msg->left_hands.push_back(left_hands);
68 msg->right_hands.push_back(right_hands);
69 msg->left_hand_type.value = aimdk_msgs::msg::HandType::CLAW; // grippers (2)
70 msg->right_hand_type.value =
71 aimdk_msgs::msg::HandType::CLAW; // grippers (2)
72
73 publisher_->publish(std::move(msg));
74 }
75
76private:
77 rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
78 rclcpp::TimerBase::SharedPtr timer_;
79
80 std::vector<std::pair<float, float>> position_pairs_;
81 size_t current_index_;
82
83 rclcpp::Time last_switch_time_;
84};
85
86int main(int argc, char *argv[]) {
87 rclcpp::init(argc, argv);
88 auto hand_control_node = std::make_shared<HandControl>();
89 rclcpp::spin(hand_control_node);
90 rclcpp::shutdown();
91 return 0;
92}
6.2.6 灵巧手控制
该示例中用到了omnihand_control,通过往话题/aima/hal/joint/hand/command发布消息来控制灵巧手的运动
注意
注意⚠️ :运行本示例前需要在机器人运控计算单元(PC1)使用aima em stop-app mc关闭原生运控模块,获取机器人控制权。注意确保机器人安全
1#include <aimdk_msgs/msg/hand_command_array.hpp>
2#include <aimdk_msgs/msg/hand_type.hpp>
3#include <array>
4#include <chrono>
5#include <memory>
6#include <rclcpp/rclcpp.hpp>
7
8using namespace std::chrono_literals;
9
10class HandCommandPublisher : public rclcpp::Node {
11public:
12 HandCommandPublisher() : Node("hand_command_publisher") {
13 publisher_ = this->create_publisher<aimdk_msgs::msg::HandCommandArray>(
14 "/aima/hal/joint/hand/command", 10);
15
16 // Create a timer to publish once per second
17 timer_ = this->create_wall_timer(
18 1s, std::bind(&HandCommandPublisher::publish_command, this));
19
20 RCLCPP_INFO(this->get_logger(), "Hand control node started!");
21 }
22
23private:
24 void publish_command() {
25 auto message = aimdk_msgs::msg::HandCommandArray();
26
27 // Set header
28 message.header.stamp = this->now();
29 message.header.frame_id = "hand_command";
30
31 // Set the hand type
32 message.left_hand_type.value =
33 aimdk_msgs::msg::HandType::NIMBLE_HANDS; // dexterous hands (1)
34 message.right_hand_type.value =
35 aimdk_msgs::msg::HandType::NIMBLE_HANDS; // dexterous hands (1)
36
37 const char *left_names[10] = {"L_thumb_roll_joint", "L_thumb_abad_joint",
38 "L_thumb_mcp_joint", "L_index_abad_joint",
39 "L_index_pip_joint", "L_middle_pip_joint",
40 "L_ring_abad_joint", "L_ring_pip_joint",
41 "L_pinky_abad_joint", "L_pinky_pip_joint"};
42 const char *right_names[10] = {"R_thumb_roll_joint", "R_thumb_abad_joint",
43 "R_thumb_mcp_joint", "R_index_abad_joint",
44 "R_index_pip_joint", "R_middle_pip_joint",
45 "R_ring_abad_joint", "R_ring_pip_joint",
46 "R_pinky_abad_joint", "R_pinky_pip_joint"};
47
48 // Create left hand command array
49 message.left_hands.resize(10);
50 for (int i = 0; i < 10; i++) {
51 message.left_hands[i].name = left_names[i];
52 message.left_hands[i].position = 0.0;
53 message.left_hands[i].velocity = 0.1;
54 message.left_hands[i].acceleration = 0.0;
55 message.left_hands[i].deceleration = 0.0;
56 message.left_hands[i].effort = 0.0;
57 }
58
59 // Create right hand command array
60 message.right_hands.resize(10);
61 for (int i = 0; i < 10; i++) {
62 message.right_hands[i].name = right_names[i];
63 message.right_hands[i].position = 0.0;
64 message.right_hands[i].velocity = 0.1;
65 message.right_hands[i].acceleration = 0.0;
66 message.right_hands[i].deceleration = 0.0;
67 message.right_hands[i].effort = 0.0;
68 }
69
70 // Absolute joint targets in rad = half-grip
71 // Joint order: [thumb_roll, thumb_abad, thumb_mcp, index_abad, index_pip,
72 // middle_pip, ring_abad, ring_pip, pinky_abad, pinky_pip]
73 const std::array<double, 10> left_hand_target = {
74 -0.6, 0.8, -0.4, 0.1, 0.7, 0.7, -0.1, 0.7, -0.1, 0.7};
75 const std::array<double, 10> right_hand_target = {
76 0.6, -0.8, 0.4, -0.1, 0.7, 0.7, 0.1, 0.7, 0.1, 0.7};
77
78 if (target_finger < 10) {
79 message.right_hands[target_finger].position =
80 right_hand_target[target_finger];
81 } else {
82 int target_finger_ = target_finger - 10;
83 message.left_hands[target_finger_].position =
84 left_hand_target[target_finger_];
85 }
86
87 // Publish the message
88 publisher_->publish(message);
89
90 RCLCPP_INFO(this->get_logger(),
91 "Published hand command with target_finger: %d", target_finger);
92
93 update_target_finger();
94 }
95
96 void update_target_finger() {
97 if (increasing_) {
98 target_finger += step_;
99 if (target_finger >= 19) {
100 target_finger = 19;
101 increasing_ = false;
102 }
103 } else {
104 target_finger -= step_;
105 if (target_finger <= 0) {
106 target_finger = 0;
107 increasing_ = true;
108 }
109 }
110 }
111
112 rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
113 rclcpp::TimerBase::SharedPtr timer_;
114
115 int target_finger = 0;
116 int step_ = 1;
117 bool increasing_ = true;
118};
119
120int main(int argc, char **argv) {
121 rclcpp::init(argc, argv);
122 auto node = std::make_shared<HandCommandPublisher>();
123 rclcpp::spin(node);
124 rclcpp::shutdown();
125 return 0;
126}
6.2.7 灵巧手触觉数据(T2.1支持)
该示例中用到了 omnihand_touch,通过订阅话题/aima/hal/joint/hand/state来接收灵巧手指尖压力传感器的数据
注意
注意⚠️ :运行本示例前需要在机器人运控计算单元(PC1)使用aima em stop-app mc关闭原生运控模块,获取机器人控制权。注意确保机器人安全
1//
2// Created by agiuser on 2026/1/27.
3//
4#include <aimdk_msgs/msg/hand_command_array.hpp>
5#include <aimdk_msgs/msg/hand_state_array.hpp>
6#include <aimdk_msgs/msg/hand_type.hpp>
7#include <chrono>
8#include <iomanip>
9#include <iostream>
10#include <rclcpp/rclcpp.hpp>
11
12using namespace std::chrono_literals;
13
14class HandStateSubscriber : public rclcpp::Node {
15public:
16 HandStateSubscriber() : Node("hand_state_subscriber") {
17 publisher_ = this->create_publisher<aimdk_msgs::msg::HandCommandArray>(
18 "/aima/hal/joint/hand/command", 10);
19 rclcpp::QoS qos_profile(rclcpp::KeepLast(10));
20 qos_profile.best_effort(); // Set reliability to BEST_EFFORT
21 // Create subscriber
22 subscription_ = this->create_subscription<aimdk_msgs::msg::HandStateArray>(
23 "/aima/hal/joint/hand/state", qos_profile,
24 std::bind(&HandStateSubscriber::topic_callback, this,
25 std::placeholders::_1));
26 // Create a timer to publish once per second
27 timer_ = this->create_wall_timer(
28 1s, std::bind(&HandStateSubscriber::publish_command, this));
29 RCLCPP_INFO(
30 this->get_logger(),
31 "Subscriber started, listening to /aima/hal/joint/hand/state topic...");
32 }
33
34private:
35 void topic_callback(const aimdk_msgs::msg::HandStateArray::SharedPtr msg) {
36 // Print message header information
37 RCLCPP_INFO(this->get_logger(),
38 "Message received - Sequence: %u, Timestamp: %d.%09d",
39 msg->header.sequence, msg->header.stamp.sec,
40 msg->header.stamp.nanosec);
41
42 // Print left hand touch sensor data
43 print_touch_sensor_data("Left Hand", msg->left_touch_sensors);
44
45 // Print right hand touch sensor data
46 print_touch_sensor_data("Right Hand", msg->right_touch_sensors);
47
48 std::cout << std::endl;
49 }
50
51 /**
52 * @brief Print touch sensor data for a specific hand
53 * @param hand_name Name of the hand (Left Hand/Right Hand)
54 * @param sensor_data Touch sensor data structure
55 */
56 void print_touch_sensor_data(
57 const std::string &hand_name,
58 const aimdk_msgs::msg::HandTouchSensorData &sensor_data) {
59 std::cout << "=== " << hand_name << " Touch Sensor Data ===" << std::endl;
60
61 // Print palm touch data
62 std::cout << "Palm Touch Data (36 elements): ";
63 print_array(sensor_data.palm_touch_data);
64
65 // Print back of hand touch data
66 std::cout << "Back of Hand Touch Data (36 elements): ";
67 print_array(sensor_data.back_of_hand_touch_data);
68
69 // Print finger touch data
70 std::cout << "Thumb Touch Data (16 elements): ";
71 print_array(sensor_data.thumb_touch_data);
72
73 std::cout << "Index Finger Touch Data (16 elements): ";
74 print_array(sensor_data.index_finger_touch_data);
75
76 std::cout << "Middle Finger Touch Data (16 elements): ";
77 print_array(sensor_data.middle_finger_touch_data);
78
79 std::cout << "Ring Finger Touch Data (16 elements): ";
80 print_array(sensor_data.ring_finger_touch_data);
81
82 std::cout << "Little Finger Touch Data (16 elements): ";
83 print_array(sensor_data.little_finger_touch_data);
84 }
85
86 /**
87 * @brief Print array of 36 uint8_t elements
88 * @param arr Array to print
89 */
90 void print_array(const std::array<uint8_t, 36> &arr) {
91 std::cout << "[";
92 for (size_t i = 0; i < arr.size(); ++i) {
93 std::cout << std::setw(3) << static_cast<int>(arr[i]);
94 if (i < arr.size() - 1)
95 std::cout << " ";
96 }
97 std::cout << "]" << std::endl;
98 }
99
100 /**
101 * @brief Print array of 16 uint8_t elements
102 * @param arr Array to print
103 */
104 void print_array(const std::array<uint8_t, 16> &arr) {
105 std::cout << "[";
106 for (size_t i = 0; i < arr.size(); ++i) {
107 std::cout << std::setw(3) << static_cast<int>(arr[i]);
108 if (i < arr.size() - 1)
109 std::cout << " ";
110 }
111 std::cout << "]" << std::endl;
112 }
113
114 void publish_command() {
115 auto message = aimdk_msgs::msg::HandCommandArray();
116
117 // Set header
118 message.header.stamp = this->now();
119 message.header.frame_id = "hand_command";
120
121 // Set the hand type
122 message.left_hand_type.value =
123 aimdk_msgs::msg::HandType::NIMBLE_HANDS; // dexterous hands (1)
124 message.right_hand_type.value =
125 aimdk_msgs::msg::HandType::NIMBLE_HANDS; // dexterous hands (1)
126
127 // Joint names for readability, mirroring the /aima/hal/joint/hand/state
128 // feedback (URDF names). The driver routes by array index/order (see
129 // endeffector.md), not by these name strings.
130 const char *left_names[10] = {"L_thumb_roll_joint", "L_thumb_abad_joint",
131 "L_thumb_mcp_joint", "L_index_abad_joint",
132 "L_index_pip_joint", "L_middle_pip_joint",
133 "L_ring_abad_joint", "L_ring_pip_joint",
134 "L_pinky_abad_joint", "L_pinky_pip_joint"};
135 const char *right_names[10] = {"R_thumb_roll_joint", "R_thumb_abad_joint",
136 "R_thumb_mcp_joint", "R_index_abad_joint",
137 "R_index_pip_joint", "R_middle_pip_joint",
138 "R_ring_abad_joint", "R_ring_pip_joint",
139 "R_pinky_abad_joint", "R_pinky_pip_joint"};
140
141 // Create left hand command array
142 message.left_hands.resize(10);
143 for (int i = 0; i < 10; i++) {
144 message.left_hands[i].name = left_names[i];
145 message.left_hands[i].position = 0.0;
146 message.left_hands[i].velocity = 0.1;
147 message.left_hands[i].acceleration = 0.0;
148 message.left_hands[i].deceleration = 0.0;
149 message.left_hands[i].effort = 0.0;
150 }
151
152 // Create right hand command array
153 message.right_hands.resize(10);
154 for (int i = 0; i < 10; i++) {
155 message.right_hands[i].name = right_names[i];
156 message.right_hands[i].position = 0.0;
157 message.right_hands[i].velocity = 0.1;
158 message.right_hands[i].acceleration = 0.0;
159 message.right_hands[i].deceleration = 0.0;
160 message.right_hands[i].effort = 0.0;
161 }
162
163 // Publish the message
164 publisher_->publish(message);
165
166 RCLCPP_INFO(this->get_logger(), "Published hand command");
167 }
168
169 rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
170 rclcpp::Subscription<aimdk_msgs::msg::HandStateArray>::SharedPtr
171 subscription_;
172 rclcpp::TimerBase::SharedPtr timer_;
173};
174
175int main(int argc, char *argv[]) {
176 rclcpp::init(argc, argv);
177 auto node = std::make_shared<HandStateSubscriber>();
178 rclcpp::spin(node);
179 rclcpp::shutdown();
180 return 0;
181}
压力传感器位于五个手指的指尖、手心、手背。需要给灵巧手发送至少一个运动指令,才能获得对应的压力数据上报。
6.2.8 注册二开输入源
对于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 <cinttypes>
10#include <memory>
11#include <signal.h>
12
13namespace {
14const char *action_name(int32_t value) {
15 using A = aimdk_msgs::msg::McInputAction;
16 switch (value) {
17 case A::INPUTACTION_ADD:
18 return "ADD";
19 case A::INPUTACTION_MODIFY:
20 return "MODIFY";
21 case A::INPUTACTION_DELETE:
22 return "DELETE";
23 case A::INPUTACTION_ENABLE:
24 return "ENABLE";
25 case A::INPUTACTION_DISABLE:
26 return "DISABLE";
27 default:
28 return "UNKNOWN";
29 }
30}
31} // namespace
32
33std::shared_ptr<rclcpp::Node> g_node = nullptr;
34
35void signal_handler(int signal) {
36 if (g_node) {
37 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
38 signal);
39 g_node.reset();
40 }
41 rclcpp::shutdown();
42 exit(signal);
43}
44
45class McInputClient : public rclcpp::Node {
46public:
47 McInputClient() : Node("set_mc_input_source_client") {
48 client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
49 "/aimdk_5Fmsgs/srv/SetMcInputSource");
50
51 RCLCPP_INFO(this->get_logger(), "✅ SetMcInputSource client node created.");
52
53 // Wait for the service to become available
54 while (!client_->wait_for_service(std::chrono::seconds(2))) {
55 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
56 }
57 RCLCPP_INFO(this->get_logger(),
58 "🟢 Service available, ready to send request.");
59 }
60
61 bool send_request() {
62 try {
63 auto request =
64 std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
65
66 // Set request data
67 request->action.value =
68 aimdk_msgs::msg::McInputAction::INPUTACTION_ADD; // ADD (1001)
69 request->input_source.name = "node.set_mc_input_source";
70 request->input_source.priority = 40;
71 request->input_source.timeout = 1000;
72
73 RCLCPP_INFO(this->get_logger(), "📨 Sending input source request: %s (%d)",
74 action_name(request->action.value), request->action.value);
75
76 auto timeout = std::chrono::milliseconds(250);
77 for (int i = 0; i < 8; i++) {
78 // Set header timestamp
79 request->request.header.stamp = this->now(); // use Node::now()
80 auto future = client_->async_send_request(request);
81 auto retcode = rclcpp::spin_until_future_complete(
82 this->shared_from_this(), future, timeout);
83 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
84 // retry as remote peer is NOT handled well by ROS
85 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
86 continue;
87 }
88 // future.done
89 auto response = future.get();
90 auto code = response->response.header.code;
91 if (code == 0) {
92 RCLCPP_INFO(this->get_logger(),
93 "✅ Input source set successfully. task_id=%" PRIu64,
94 response->response.task_id);
95 return true;
96 } else {
97 RCLCPP_ERROR(
98 this->get_logger(),
99 "❌ Input source set failed. ret_code=%" PRId64
100 ", task_id=%" PRIu64 " "
101 "(duplicated ADD? or MODIFY/ENABLE/DISABLE for unknown source?)",
102 code, response->response.task_id);
103 return false;
104 }
105 }
106 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
107 return false;
108 } catch (const std::exception &e) {
109 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
110 return false;
111 }
112 }
113
114private:
115 rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr client_;
116};
117
118int main(int argc, char *argv[]) {
119 try {
120 rclcpp::init(argc, argv);
121 signal(SIGINT, signal_handler);
122 signal(SIGTERM, signal_handler);
123
124 g_node = std::make_shared<McInputClient>();
125 auto client = std::dynamic_pointer_cast<McInputClient>(g_node);
126
127 if (client) {
128 client->send_request();
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}
6.2.9 获取当前输入源
该示例中用到了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 <cinttypes>
7#include <memory>
8#include <signal.h>
9
10// Global node object
11std::shared_ptr<rclcpp::Node> g_node = nullptr;
12
13// Signal handler
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
24// Client Class
25class GetCurrentInputSourceClient : public rclcpp::Node {
26public:
27 GetCurrentInputSourceClient() : Node("get_current_input_source_client") {
28
29 client_ = this->create_client<aimdk_msgs::srv::GetCurrentInputSource>(
30 "/aimdk_5Fmsgs/srv/GetCurrentInputSource");
31
32 RCLCPP_INFO(this->get_logger(),
33 "✅ GetCurrentInputSource 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 void send_request() {
44 try {
45 auto request =
46 std::make_shared<aimdk_msgs::srv::GetCurrentInputSource::Request>();
47 request->request = aimdk_msgs::msg::CommonRequest();
48
49 RCLCPP_INFO(this->get_logger(),
50 "📨 Sending request to get current input source");
51
52 auto timeout = std::chrono::milliseconds(250);
53
54 for (int i = 0; i < 8; i++) {
55 request->request.header.stamp = this->now();
56 auto future = client_->async_send_request(request);
57 auto retcode = rclcpp::spin_until_future_complete(
58 this->shared_from_this(), future, timeout);
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->response.header.code == 0) {
67 RCLCPP_INFO(this->get_logger(),
68 "✅ Current input source get successfully:");
69 RCLCPP_INFO(this->get_logger(), "Name: %s",
70 response->input_source.name.c_str());
71 RCLCPP_INFO(this->get_logger(), "Priority: %d",
72 response->input_source.priority);
73 RCLCPP_INFO(this->get_logger(), "Timeout: %d",
74 response->input_source.timeout);
75 } else {
76 RCLCPP_WARN(
77 this->get_logger(),
78 "❌ Current input source get failed, return code: %" PRId64,
79 response->response.header.code);
80 }
81 return;
82 }
83 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
84 } catch (const std::exception &e) {
85 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
86 }
87 }
88
89private:
90 rclcpp::Client<aimdk_msgs::srv::GetCurrentInputSource>::SharedPtr client_;
91};
92
93int main(int argc, char *argv[]) {
94 try {
95 rclcpp::init(argc, argv);
96
97 signal(SIGINT, signal_handler);
98 signal(SIGTERM, signal_handler);
99
100 g_node = std::make_shared<GetCurrentInputSourceClient>();
101 auto client =
102 std::dynamic_pointer_cast<GetCurrentInputSourceClient>(g_node);
103
104 if (client) {
105 client->send_request();
106 }
107
108 g_node.reset();
109 rclcpp::shutdown();
110 return 0;
111 } catch (const std::exception &e) {
112 RCLCPP_ERROR(rclcpp::get_logger("main"),
113 "Program exited with exception: %s", e.what());
114 return 1;
115 }
116}
使用说明
# 获取当前输入源信息
ros2 run examples get_current_input_source
输出示例
[INFO] [1779174540.534817548] [get_current_input_source_client]: ✅ GetCurrentInputSource client node created.
[INFO] [1779174541.371095227] [get_current_input_source_client]: 🟢 Service available, ready to send request.
[INFO] [1779174541.371216694] [get_current_input_source_client]: 📨 Sending request to get current input source
[INFO] [1779174541.382602338] [get_current_input_source_client]: ✅ Current input source get successfully:
[INFO] [1779174541.382692894] [get_current_input_source_client]: Name: node
[INFO] [1779174541.382713533] [get_current_input_source_client]: Priority: 40
[INFO] [1779174541.382726205] [get_current_input_source_client]: Timeout: 1000
注意事项
确保GetCurrentInputSource服务正常运行
需要在注册输入源之后才能获取到有效信息
状态码为0表示查询成功
6.2.10 机器人走跑控制
该示例中用到了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 <cmath>
13#include <iostream>
14#include <memory>
15#include <signal.h>
16#include <thread>
17
18class DirectVelocityControl : public rclcpp::Node {
19public:
20 DirectVelocityControl() : Node("direct_velocity_control") {
21 // Create publisher
22 publisher_ = this->create_publisher<aimdk_msgs::msg::McLocomotionVelocity>(
23 "/aima/mc/locomotion/velocity", 10);
24 // Create service client
25 client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
26 "/aimdk_5Fmsgs/srv/SetMcInputSource");
27
28 // Maximum speed limits
29 max_forward_speed_ = 1.0; // m/s
30 max_lateral_speed_ = 1.0; // m/s
31 max_angular_speed_ = 1.0; // rad/s
32 // Minimum speed limits (0 is also OK)
33 min_forward_speed_ = 0.2; // m/s
34 min_lateral_speed_ = 0.2; // m/s
35 min_angular_speed_ = 0.1; // rad/s
36
37 RCLCPP_INFO(this->get_logger(), "Direct velocity control node started.");
38 }
39
40 void start_publish() {
41 if (timer_ != nullptr) {
42 return;
43 }
44 // Set timer to periodically publish velocity messages (50Hz)
45 timer_ = this->create_wall_timer(
46 std::chrono::milliseconds(20),
47 std::bind(&DirectVelocityControl::publish_velocity, this));
48 }
49
50 bool register_input_source() {
51 const std::chrono::seconds timeout(8);
52 auto start_time = std::chrono::steady_clock::now();
53 while (!client_->wait_for_service(std::chrono::seconds(2))) {
54 if (std::chrono::steady_clock::now() - start_time > timeout) {
55 RCLCPP_ERROR(this->get_logger(), "Waiting for service timed out");
56 return false;
57 }
58 RCLCPP_INFO(this->get_logger(), "Waiting for input source service...");
59 }
60
61 // Try ADD first; if it fails (likely already registered), fall back to
62 // ENABLE
63 if (call_input_source( // ADD (1001)
64 aimdk_msgs::msg::McInputAction::INPUTACTION_ADD)) {
65 RCLCPP_INFO(this->get_logger(), "✅ Input source registered (ADD).");
66 return true;
67 }
68 RCLCPP_INFO(this->get_logger(),
69 "ADD failed (likely already registered), trying ENABLE...");
70 if (call_input_source( // ENABLE (2001)
71 aimdk_msgs::msg::McInputAction::INPUTACTION_ENABLE)) {
72 RCLCPP_INFO(this->get_logger(), "✅ Input source enabled.");
73 return true;
74 }
75 RCLCPP_ERROR(this->get_logger(),
76 "❌ Failed to register input source via ADD or ENABLE.");
77 return false;
78 }
79
80 bool call_input_source(int32_t action_value) {
81 auto request =
82 std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
83 request->action.value = action_value;
84 request->input_source.name = "node.locomotion_velocity";
85 request->input_source.priority = 40;
86 request->input_source.timeout = 1000;
87
88 auto timeout2 = std::chrono::milliseconds(250);
89
90 for (int i = 0; i < 8; i++) {
91 request->request.header.stamp = this->now();
92 auto future = client_->async_send_request(request);
93 auto retcode = rclcpp::spin_until_future_complete(
94 this->shared_from_this(), future, timeout2);
95 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
96 // retry as remote peer is NOT handled well by ROS
97 RCLCPP_INFO(
98 this->get_logger(), "trying input source action %s (%d)... [%d]",
99 action_value == aimdk_msgs::msg::McInputAction::INPUTACTION_ADD
100 ? "ADD"
101 : "ENABLE",
102 action_value, i);
103 continue;
104 }
105 auto response = future.get();
106 return response->response.header.code == 0;
107 }
108 return false;
109 }
110
111 void publish_velocity() {
112 auto msg = std::make_unique<aimdk_msgs::msg::McLocomotionVelocity>();
113 msg->header = aimdk_msgs::msg::MessageHeader();
114 msg->header.stamp = this->now();
115 msg->source = "node.locomotion_velocity"; // Set message source
116 msg->forward_velocity = forward_velocity_;
117 msg->lateral_velocity = lateral_velocity_;
118 msg->angular_velocity = angular_velocity_;
119
120 publisher_->publish(std::move(msg));
121 RCLCPP_INFO(this->get_logger(),
122 "Published velocity: Forward %.2f m/s, Lateral %.2f m/s, "
123 "Angular %.2f rad/s",
124 forward_velocity_, lateral_velocity_, angular_velocity_);
125 }
126
127 void clear_velocity() {
128 forward_velocity_ = 0.0;
129 lateral_velocity_ = 0.0;
130 angular_velocity_ = 0.0;
131 }
132
133 bool set_forward(double forward) {
134 // check value range, mc has thresholds to start movement
135 if (std::abs(forward) < 0.005) {
136 forward_velocity_ = 0.0;
137 return true;
138 } else if ((std::abs(forward) > max_forward_speed_) ||
139 (std::abs(forward) < min_forward_speed_)) {
140 RCLCPP_ERROR(this->get_logger(), "input value out of range, exiting");
141 return false;
142 } else {
143 forward_velocity_ = forward;
144 return true;
145 }
146 }
147
148 bool set_lateral(double lateral) {
149 // check value range, mc has thresholds to start movement
150 if (std::abs(lateral) < 0.005) {
151 lateral_velocity_ = 0.0;
152 return true;
153 } else if ((std::abs(lateral) > max_lateral_speed_) ||
154 (std::abs(lateral) < min_lateral_speed_)) {
155 RCLCPP_ERROR(this->get_logger(), "input value out of range, exiting");
156 return false;
157 } else {
158 lateral_velocity_ = lateral;
159 return true;
160 }
161 }
162
163 bool set_angular(double angular) {
164 // check value range, mc has thresholds to start movement
165 if (std::abs(angular) < 0.005) {
166 angular_velocity_ = 0.0;
167 return true;
168 } else if ((std::abs(angular) > max_angular_speed_) ||
169 (std::abs(angular) < min_angular_speed_)) {
170 RCLCPP_ERROR(this->get_logger(), "input value out of range, exiting");
171 return false;
172 } else {
173 angular_velocity_ = angular;
174 return true;
175 }
176 }
177
178private:
179 rclcpp::Publisher<aimdk_msgs::msg::McLocomotionVelocity>::SharedPtr
180 publisher_;
181 rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr client_;
182 rclcpp::TimerBase::SharedPtr timer_;
183
184 double forward_velocity_;
185 double lateral_velocity_;
186 double angular_velocity_;
187
188 double max_forward_speed_;
189 double max_lateral_speed_;
190 double max_angular_speed_;
191
192 double min_forward_speed_;
193 double min_lateral_speed_;
194 double min_angular_speed_;
195};
196
197// Signal Processing
198std::shared_ptr<DirectVelocityControl> global_node = nullptr;
199void signal_handler(int sig) {
200 if (global_node) {
201 global_node->clear_velocity();
202 RCLCPP_INFO(global_node->get_logger(),
203 "Received signal %d: clearing velocity and shutting down", sig);
204 }
205 rclcpp::shutdown();
206 exit(sig);
207}
208
209int main(int argc, char *argv[]) {
210 rclcpp::init(argc, argv);
211 signal(SIGINT, signal_handler);
212 signal(SIGTERM, signal_handler);
213
214 global_node = std::make_shared<DirectVelocityControl>();
215 auto node = global_node;
216
217 if (!node->register_input_source()) {
218 RCLCPP_ERROR(node->get_logger(),
219 "❌ Input source registration failed, exiting");
220 global_node.reset();
221 rclcpp::shutdown();
222 return 1;
223 }
224
225 // get and check control values
226 // notice that mc has thresholds to start movement
227 double forward, lateral, angular;
228 std::cout << "Enter forward speed 0 or ±(0.2 ~ 1.0) m/s: ";
229 std::cin >> forward;
230 if (!node->set_forward(forward)) {
231 return 2;
232 }
233 std::cout << "Enter lateral speed 0 or ±(0.2 ~ 1.0) m/s: ";
234 std::cin >> lateral;
235 if (!node->set_lateral(lateral)) {
236 return 2;
237 }
238 std::cout << "Enter angular speed 0 or ±(0.1 ~ 1.0) rad/s: ";
239 std::cin >> angular;
240 if (!node->set_angular(angular)) {
241 return 2;
242 }
243
244 RCLCPP_INFO(node->get_logger(), "Setting velocity; moving for 5 seconds");
245
246 node->start_publish();
247
248 auto start_time = node->now();
249 while ((node->now() - start_time).seconds() < 5.0) {
250 rclcpp::spin_some(node);
251 std::this_thread::sleep_for(std::chrono::milliseconds(1));
252 }
253
254 node->clear_velocity();
255 RCLCPP_INFO(node->get_logger(), "5 seconds elapsed; robot stopped");
256
257 rclcpp::spin(node);
258 rclcpp::shutdown();
259 return 0;
260}
6.2.11 关节电机控制
本示例展示了如何使用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函数添加新的控制回调函数
调整控制频率:
修改
timer的周期(当前为3s)和ruckig的dt(当前为0.002s)
1#include <aimdk_msgs/msg/joint_command_array.hpp>
2#include <aimdk_msgs/msg/joint_state_array.hpp>
3#include <atomic>
4#include <cmath>
5#include <cstdlib>
6#include <map>
7#include <memory>
8#include <rclcpp/rclcpp.hpp>
9#include <ruckig/ruckig.hpp>
10#include <signal.h>
11#include <string>
12#include <thread>
13#include <vector>
14
15/**
16 * @brief Global variables and signal handling
17 */
18// Global variables to control program state
19std::atomic<bool> g_running(true);
20std::atomic<bool> g_emergency_stop(false);
21
22// Signal handler function
23void signal_handler(int) {
24 g_running = false;
25 RCLCPP_INFO(rclcpp::get_logger("main"),
26 "Received termination signal, shutting down...");
27}
28
29/**
30 * @brief Robot model definition
31 */
32enum class JointArea {
33 HEAD, // Head joints
34 ARM, // Arm joints
35 WAIST, // Waist joints
36 LEG, // Leg joints
37};
38
39/**
40 * @brief Joint information structure
41 */
42struct JointInfo {
43 std::string name; // Joint name
44 double lower_limit; // Joint angle lower limit
45 double upper_limit; // Joint angle upper limit
46 double kp; // Position control gain
47 double kd; // Velocity control gain
48};
49
50/**
51 * @brief Robot model configuration
52 * Contains parameters for all joints, enabling or disabling specific joints as
53 * needed
54 */
55std::map<JointArea, std::vector<JointInfo>> robot_model = {
56 {JointArea::LEG,
57 {
58 // Left leg joint configuration
59 {"left_hip_pitch_joint", -2.704, 2.556, 40.0, 4.0},
60 {"left_hip_roll_joint", -0.235, 2.906, 40.0, 4.0},
61 {"left_hip_yaw_joint", -1.684, 3.430, 30.0, 3.0},
62 {"left_knee_joint", 0.0000, 2.4073, 80.0, 8.0},
63 {"left_ankle_pitch_joint", -0.803, 0.453, 40.0, 4.0},
64 {"left_ankle_roll_joint", -0.2625, 0.2625, 20.0, 2.0},
65 // Right leg joint configuration
66 {"right_hip_pitch_joint", -2.704, 2.556, 40.0, 4.0},
67 {"right_hip_roll_joint", -2.906, 0.235, 40.0, 4.0},
68 {"right_hip_yaw_joint", -3.430, 1.684, 30.0, 3.0},
69 {"right_knee_joint", 0.0000, 2.4073, 80.0, 8.0},
70 {"right_ankle_pitch_joint", -0.803, 0.453, 40.0, 4.0},
71 {"right_ankle_roll_joint", -0.2625, 0.2625, 20.0, 2.0},
72 }},
73
74 {JointArea::WAIST,
75 {
76 // Waist joint configuration
77 {"waist_yaw_joint", -3.43, 2.382, 20.0, 4.0},
78 {"waist_pitch_joint", -0.314, 0.314, 20.0, 4.0},
79 {"waist_roll_joint", -0.488, 0.488, 20.0, 4.0},
80 }},
81 {JointArea::ARM,
82 {
83 // Left arm joint configuration
84 {"left_shoulder_pitch_joint", -3.08, 2.04, 20.0, 2.0},
85 {"left_shoulder_roll_joint", -0.061, 2.993, 20.0, 2.0},
86 {"left_shoulder_yaw_joint", -2.556, 2.556, 20.0, 2.0},
87 {"left_elbow_joint", -2.3556, 0.0, 20.0, 2.0},
88 {"left_wrist_yaw_joint", -2.556, 2.556, 20.0, 2.0},
89 {"left_wrist_pitch_joint", -0.558, 0.558, 20.0, 2.0},
90 {"left_wrist_roll_joint", -1.571, 0.724, 20.0, 2.0},
91 // Right arm joint configuration
92 {"right_shoulder_pitch_joint", -3.08, 2.04, 20.0, 2.0},
93 {"right_shoulder_roll_joint", -2.993, 0.061, 20.0, 2.0},
94 {"right_shoulder_yaw_joint", -2.556, 2.556, 20.0, 2.0},
95 {"right_elbow_joint", -2.3556, 0.0000, 20.0, 2.0},
96 {"right_wrist_yaw_joint", -2.556, 2.556, 20.0, 2.0},
97 {"right_wrist_pitch_joint", -0.558, 0.558, 20.0, 2.0},
98 {"right_wrist_roll_joint", -0.724, 1.571, 20.0, 2.0},
99 }},
100 {JointArea::HEAD,
101 {
102 // Head joint configuration
103 {"head_yaw_joint", -0.366, 0.366, 20.0, 2.0},
104 {"head_pitch_joint", -0.3838, 0.3838, 20.0, 2.0},
105 }},
106};
107
108/**
109 * @brief Joint controller node class
110 * @tparam DOFs Degrees of freedom
111 * @tparam Area Joint area
112 */
113template <int DOFs, JointArea Area>
114class JointControllerNode : public rclcpp::Node {
115public:
116 /**
117 * @brief Constructor
118 * @param node_name Node name
119 * @param sub_topic Subscription topic name
120 * @param pub_topic Publication topic name
121 * @param qos QoS configuration
122 */
123 JointControllerNode(std::string node_name, std::string sub_topic,
124 std::string pub_topic,
125 rclcpp::QoS qos = rclcpp::SensorDataQoS())
126 : Node(node_name), ruckig(0.002) {
127 joint_info_ = robot_model[Area];
128 if (joint_info_.size() != DOFs) {
129 RCLCPP_ERROR(this->get_logger(), "Joint count mismatch.");
130 exit(1);
131 }
132
133 // Set motion constraints for Ruckig trajectory planner
134 for (int i = 0; i < DOFs; ++i) {
135 input.max_velocity[i] = 1.0; // Max velocity limit
136 input.max_acceleration[i] = 1.0; // Max acceleration limit
137 input.max_jerk[i] = 25.0; // Max jerk (change of acceleration) limit
138 }
139
140 // Create joint state subscriber
141 sub_ = this->create_subscription<aimdk_msgs::msg::JointStateArray>(
142 sub_topic, qos,
143 std::bind(&JointControllerNode::JointStateCallback, this,
144 std::placeholders::_1));
145
146 // Create joint command publisher
147 pub_ = this->create_publisher<aimdk_msgs::msg::JointCommandArray>(pub_topic,
148 qos);
149 }
150
151private:
152 // Ruckig trajectory planner variables
153 ruckig::Ruckig<DOFs> ruckig; // Trajectory planner instance
154 ruckig::InputParameter<DOFs> input; // Input parameters
155 ruckig::OutputParameter<DOFs> output; // Output parameters
156 bool ruckig_initialized_ = false; // Trajectory planner initialization flag
157 std::vector<JointInfo> joint_info_; // Joint information list
158
159 // ROS communication variables
160 rclcpp::Subscription<aimdk_msgs::msg::JointStateArray>::SharedPtr
161 sub_; // State subscriber
162 rclcpp::Publisher<aimdk_msgs::msg::JointCommandArray>::SharedPtr
163 pub_; // Command publisher
164
165 /**
166 * @brief Joint state callback function
167 * @param msg Joint state message
168 */
169 void
170 JointStateCallback(const aimdk_msgs::msg::JointStateArray::SharedPtr msg) {
171 // Initialize trajectory planner on first state reception
172 if (!ruckig_initialized_) {
173 for (int i = 0; i < DOFs; ++i) {
174 input.current_position[i] = msg->joints[i].position;
175 input.current_velocity[i] = msg->joints[i].velocity;
176 input.current_acceleration[i] = 0.0;
177 }
178 ruckig_initialized_ = true;
179 RCLCPP_INFO(this->get_logger(),
180 "Ruckig trajectory planner initialization complete");
181 }
182 }
183
184public:
185 /**
186 * @brief Set target joint position
187 * @param joint_name Joint name
188 * @param target_position Target position
189 * @return Whether the target position was successfully set
190 */
191 bool SetTargetPosition(std::string joint_name, double target_position) {
192 if (!ruckig_initialized_) {
193 RCLCPP_WARN(this->get_logger(),
194 "Ruckig trajectory planner not initialized");
195 return false;
196 }
197
198 // Find target joint and set its position
199 int target_joint = -1;
200 for (int i = 0; i < DOFs; ++i) {
201 if (joint_info_[i].name == joint_name) {
202 // Check if target position is within limits
203 if (target_position < joint_info_[i].lower_limit ||
204 target_position > joint_info_[i].upper_limit) {
205 RCLCPP_ERROR(
206 this->get_logger(),
207 "Target position %.3f exceeds limit for joint %s [%.3f, %.3f]",
208 target_position, joint_name.c_str(), joint_info_[i].lower_limit,
209 joint_info_[i].upper_limit);
210 return false;
211 }
212 input.target_position[i] = target_position;
213 input.target_velocity[i] = 0.0;
214 input.target_acceleration[i] = 0.0;
215 target_joint = i;
216 } else {
217 input.target_position[i] = input.current_position[i];
218 input.target_velocity[i] = 0.0;
219 input.target_acceleration[i] = 0.0;
220 }
221 }
222
223 if (target_joint == -1) {
224 RCLCPP_ERROR(this->get_logger(), "Joint %s not found",
225 joint_name.c_str());
226 return false;
227 }
228
229 // Perform trajectory planning and send command using Ruckig
230 const double tolerance = 1e-6;
231 while (g_running && rclcpp::ok() && !g_emergency_stop) {
232 auto result = ruckig.update(input, output);
233 if (result != ruckig::Result::Working &&
234 result != ruckig::Result::Finished) {
235 RCLCPP_WARN(this->get_logger(), "Trajectory planning failed");
236 break;
237 }
238
239 // Update current state
240 for (int i = 0; i < DOFs; ++i) {
241 input.current_position[i] = output.new_position[i];
242 input.current_velocity[i] = output.new_velocity[i];
243 input.current_acceleration[i] = output.new_acceleration[i];
244 }
245
246 // Check if target position is reached
247 if (std::abs(output.new_position[target_joint] - target_position) <
248 tolerance) {
249 RCLCPP_INFO(this->get_logger(), "Joint %s reached target position",
250 joint_name.c_str());
251 break;
252 }
253
254 // Create and send joint command
255 aimdk_msgs::msg::JointCommandArray cmd;
256 cmd.joints.resize(DOFs);
257 for (int i = 0; i < DOFs; ++i) {
258 auto &joint = joint_info_[i];
259 cmd.joints[i].name = joint.name;
260 cmd.joints[i].position = output.new_position[i];
261 cmd.joints[i].velocity = output.new_velocity[i];
262 cmd.joints[i].stiffness = joint.kp;
263 cmd.joints[i].damping = joint.kd;
264 }
265 pub_->publish(cmd);
266
267 // Short delay to avoid excessive CPU usage
268 std::this_thread::sleep_for(std::chrono::milliseconds(2));
269 }
270
271 return true;
272 }
273
274 /**
275 * @brief Safely stop all joints
276 */
277 void safe_stop() {
278 if (!ruckig_initialized_) {
279 RCLCPP_WARN(this->get_logger(), "Ruckig trajectory planner not "
280 "initialized, cannot perform safe stop");
281 return;
282 }
283
284 RCLCPP_INFO(this->get_logger(), "Performing safe stop...");
285
286 // Set all joint target positions to current positions
287 for (int i = 0; i < DOFs; ++i) {
288 input.target_position[i] = input.current_position[i];
289 input.target_velocity[i] = 0.0;
290 input.target_acceleration[i] = 0.0;
291 }
292
293 // Send final command to ensure joints stop
294 aimdk_msgs::msg::JointCommandArray cmd;
295 cmd.joints.resize(DOFs);
296 for (int i = 0; i < DOFs; ++i) {
297 auto &joint = joint_info_[i];
298 cmd.joints[i].name = joint.name;
299 cmd.joints[i].position = input.current_position[i];
300 cmd.joints[i].velocity = 0.0;
301 cmd.joints[i].stiffness = joint.kp;
302 cmd.joints[i].damping = joint.kd;
303 }
304 pub_->publish(cmd);
305
306 RCLCPP_INFO(this->get_logger(), "Safe stop complete");
307 }
308
309 /**
310 * @brief Emergency stop for all joints
311 */
312 void emergency_stop() {
313 g_emergency_stop = true;
314 safe_stop();
315 RCLCPP_ERROR(this->get_logger(), "Emergency stop triggered");
316 }
317};
318
319/**
320 * @brief Main function
321 */
322int main(int argc, char *argv[]) {
323 rclcpp::init(argc, argv);
324
325 // Set up signal handling
326 signal(SIGINT, signal_handler);
327 signal(SIGTERM, signal_handler);
328
329 try {
330 // Create leg controller node
331 auto leg_node = std::make_shared<JointControllerNode<12, JointArea::LEG>>(
332 "leg_node", "/aima/hal/joint/leg/state", "/aima/hal/joint/leg/command");
333
334 // Create timer node
335 rclcpp::Node::SharedPtr timer_node =
336 rclcpp::Node::make_shared("timer_node");
337 double position = 0.8;
338
339 // Create timer callback function
340 auto timer = timer_node->create_wall_timer(std::chrono::seconds(3), [&]() {
341 if (!g_running || g_emergency_stop)
342 return; // If the program is shutting down or emergency stopped, do not
343 // execute new actions
344 position = -position;
345 position = 1.3 + position;
346 if (!leg_node->SetTargetPosition("left_knee_joint", position)) {
347 RCLCPP_ERROR(rclcpp::get_logger("main"),
348 "Failed to set target position");
349 }
350 });
351
352 // Create executor
353 rclcpp::executors::MultiThreadedExecutor executor;
354 executor.add_node(leg_node);
355 executor.add_node(timer_node);
356
357 // Main loop
358 while (g_running && rclcpp::ok() && !g_emergency_stop) {
359 executor.spin_once(std::chrono::milliseconds(100));
360 }
361
362 // Safely stop all joints
363 RCLCPP_INFO(rclcpp::get_logger("main"), "Safely stopping all joints...");
364 leg_node->safe_stop();
365
366 // Wait a short time to ensure command transmission is complete
367 std::this_thread::sleep_for(std::chrono::milliseconds(100));
368
369 // Clean up resources
370 RCLCPP_INFO(rclcpp::get_logger("main"), "Cleaning up resources...");
371 leg_node.reset();
372 timer_node.reset();
373
374 } catch (const std::exception &e) {
375 RCLCPP_ERROR(rclcpp::get_logger("main"), "Exception occurred: %s",
376 e.what());
377 g_emergency_stop = true;
378 } catch (...) {
379 RCLCPP_ERROR(rclcpp::get_logger("main"), "Unknown exception occurred");
380 g_emergency_stop = true;
381 }
382
383 RCLCPP_INFO(rclcpp::get_logger("main"), "Program exited safely");
384 rclcpp::shutdown();
385 return 0;
386}
6.2.12 键盘控制机器人
本示例实现了通过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 <curses.h>
13#include <rclcpp/rclcpp.hpp>
14
15using aimdk_msgs::msg::McLocomotionVelocity;
16using std::placeholders::_1;
17
18class KeyboardVelocityController : public rclcpp::Node {
19public:
20 KeyboardVelocityController()
21 : Node("keyboard_velocity_controller"), forward_velocity_(0.0),
22 lateral_velocity_(0.0), angular_velocity_(0.0), step_(0.2),
23 angular_step_(0.1) {
24 pub_ = this->create_publisher<McLocomotionVelocity>(
25 "/aima/mc/locomotion/velocity", 10);
26 client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
27 "/aimdk_5Fmsgs/srv/SetMcInputSource");
28 // Register input source
29 if (!register_input_source()) {
30 RCLCPP_ERROR(this->get_logger(),
31 "❌ Input source registration failed, exiting");
32 throw std::runtime_error("Input source registration failed");
33 }
34 // Initialize ncurses
35 initscr();
36 cbreak();
37 noecho();
38 keypad(stdscr, TRUE);
39 nodelay(stdscr, TRUE);
40
41 timer_ = this->create_wall_timer(
42 std::chrono::milliseconds(50),
43 std::bind(&KeyboardVelocityController::checkKeyAndPublish, this));
44
45 RCLCPP_INFO(this->get_logger(),
46 "Control started: W/S Forward/Backward | A/D Strafe Left/Right "
47 "| Q/E Turn Left/Right | Space Stop | ESC Exit");
48 }
49
50 ~KeyboardVelocityController() {
51 endwin(); // Restore terminal
52 }
53
54private:
55 rclcpp::Publisher<McLocomotionVelocity>::SharedPtr pub_;
56 rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr client_;
57 rclcpp::TimerBase::SharedPtr timer_;
58
59 float forward_velocity_, lateral_velocity_, angular_velocity_;
60 const float step_, angular_step_;
61
62 bool register_input_source() {
63 const std::chrono::seconds srv_timeout(8);
64 auto start_time = std::chrono::steady_clock::now();
65 while (!client_->wait_for_service(std::chrono::seconds(2))) {
66 if (std::chrono::steady_clock::now() - start_time > srv_timeout) {
67 RCLCPP_ERROR(this->get_logger(), "Waiting for service timed out");
68 return false;
69 }
70 RCLCPP_INFO(this->get_logger(), "Waiting for input source service...");
71 }
72
73 // Try ADD first; if it fails (likely already registered), fall back to
74 // ENABLE
75 if (call_input_source( // ADD (1001)
76 aimdk_msgs::msg::McInputAction::INPUTACTION_ADD)) {
77 RCLCPP_INFO(this->get_logger(), "✅ Input source registered (ADD).");
78 return true;
79 }
80 RCLCPP_INFO(this->get_logger(),
81 "ADD failed (likely already registered), trying ENABLE...");
82 if (call_input_source( // ENABLE (2001)
83 aimdk_msgs::msg::McInputAction::INPUTACTION_ENABLE)) {
84 RCLCPP_INFO(this->get_logger(), "✅ Input source enabled.");
85 return true;
86 }
87 RCLCPP_ERROR(this->get_logger(),
88 "❌ Failed to register input source via ADD or ENABLE.");
89 return false;
90 }
91
92 bool call_input_source(int32_t action_value) {
93 auto request =
94 std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
95 request->action.value = action_value;
96 request->input_source.name = "node.keyboard";
97 request->input_source.priority = 40;
98 request->input_source.timeout = 1000;
99
100 auto timeout = std::chrono::milliseconds(250);
101
102 for (int i = 0; i < 8; i++) {
103 request->request.header.stamp = this->now();
104 auto future = client_->async_send_request(request);
105 auto retcode = rclcpp::spin_until_future_complete(
106 this->get_node_base_interface(), future, timeout);
107 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
108 // retry as remote peer is NOT handled well by ROS
109 RCLCPP_INFO(
110 this->get_logger(), "trying input source action %s (%d)... [%d]",
111 action_value == aimdk_msgs::msg::McInputAction::INPUTACTION_ADD
112 ? "ADD"
113 : "ENABLE",
114 action_value, i);
115 continue;
116 }
117 auto response = future.get();
118 return response->response.header.code == 0;
119 }
120 return false;
121 }
122
123 void checkKeyAndPublish() {
124 int ch = getch(); // non-blocking read
125
126 switch (ch) {
127 case ' ': // Space key
128 forward_velocity_ = 0.0;
129 lateral_velocity_ = 0.0;
130 angular_velocity_ = 0.0;
131 break;
132 case 'w':
133 forward_velocity_ = std::min(forward_velocity_ + step_, 1.0f);
134 break;
135 case 's':
136 forward_velocity_ = std::max(forward_velocity_ - step_, -1.0f);
137 break;
138 case 'a':
139 lateral_velocity_ = std::min(lateral_velocity_ + step_, 1.0f);
140 break;
141 case 'd':
142 lateral_velocity_ = std::max(lateral_velocity_ - step_, -1.0f);
143 break;
144 case 'q':
145 angular_velocity_ = std::min(angular_velocity_ + angular_step_, 1.0f);
146 break;
147 case 'e':
148 angular_velocity_ = std::max(angular_velocity_ - angular_step_, -1.0f);
149 break;
150 case 27: // ESC Key
151 RCLCPP_INFO(this->get_logger(), "Exiting control");
152 rclcpp::shutdown();
153 return;
154 }
155
156 auto msg = std::make_unique<McLocomotionVelocity>();
157 msg->header = aimdk_msgs::msg::MessageHeader();
158 msg->header.stamp = this->now();
159 msg->source = "node.keyboard";
160 msg->forward_velocity = forward_velocity_;
161 msg->lateral_velocity = lateral_velocity_;
162 msg->angular_velocity = angular_velocity_;
163
164 float fwd = forward_velocity_;
165 float lat = lateral_velocity_;
166 float ang = angular_velocity_;
167
168 pub_->publish(std::move(msg));
169
170 // Screen Output
171 clear();
172 mvprintw(0, 0,
173 "W/S: Forward/Backward | A/D: Left/Right Strafe | Q/E: Turn "
174 "Left/Right | Space: Stop | ESC: Exit");
175 mvprintw(2, 0,
176 "Speed Status: Forward: %.2f m/s | Lateral: %.2f m/s | Angular: "
177 "%.2f rad/s",
178 fwd, lat, ang);
179 refresh();
180 }
181};
182
183int main(int argc, char *argv[]) {
184 rclcpp::init(argc, argv);
185 try {
186 auto node = std::make_shared<KeyboardVelocityController>();
187 rclcpp::spin(node);
188 } catch (const std::exception &e) {
189 RCLCPP_FATAL(rclcpp::get_logger("main"),
190 "Program exited with exception: %s", e.what());
191 }
192 rclcpp::shutdown();
193 return 0;
194}
6.2.13 拍照
该示例中用到了take_photo,在运行节点程序前,修改需要拍照的相机话题,启动节点程序后,会创建/images/目录,将当前帧图像保存在这个目录下。
1#include <chrono>
2#include <cv_bridge/cv_bridge.h>
3#include <filesystem>
4#include <opencv2/core.hpp>
5#include <opencv2/imgcodecs.hpp>
6#include <opencv2/imgproc.hpp>
7#include <rclcpp/rclcpp.hpp>
8#include <sensor_msgs/msg/image.hpp>
9#include <string>
10
11class SaveOneRaw : public rclcpp::Node {
12public:
13 SaveOneRaw() : Node("save_one_image"), saved_(false) {
14 topic_ = declare_parameter<std::string>(
15 "image_topic", "/aima/hal/sensor/stereo_head_front_left/rgb_image");
16
17 std::filesystem::create_directories("images");
18
19 auto qos = rclcpp::SensorDataQoS(); // BestEffort/Volatile
20 sub_ = create_subscription<sensor_msgs::msg::Image>(
21 topic_, qos, std::bind(&SaveOneRaw::cb, this, std::placeholders::_1));
22
23 RCLCPP_INFO(get_logger(), "Subscribing (raw): %s", topic_.c_str());
24 }
25
26private:
27 void cb(const sensor_msgs::msg::Image::SharedPtr msg) {
28 if (saved_)
29 return;
30
31 try {
32 // Obtain the Mat without copying by not specifying encoding
33 cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
34 cv::Mat img = cvp->image;
35
36 // Convert to BGR for uniform saving
37 if (msg->encoding == "rgb8") {
38 cv::cvtColor(img, img, cv::COLOR_RGB2BGR);
39 } else if (msg->encoding == "mono8") {
40 cv::cvtColor(img, img, cv::COLOR_GRAY2BGR);
41 } // bgr8 Use this directly; add more branches as needed to support
42 // additional encodings.
43
44 auto now = std::chrono::system_clock::now();
45 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
46 now.time_since_epoch())
47 .count();
48 std::string path = "images/frame_" + std::to_string(ms) + ".png";
49
50 if (cv::imwrite(path, img)) {
51 RCLCPP_INFO(get_logger(), "Saved: %s (%dx%d)", path.c_str(), img.cols,
52 img.rows);
53 saved_ = true;
54 rclcpp::shutdown();
55 } else {
56 RCLCPP_ERROR(get_logger(), "cv::imwrite failed: %s", path.c_str());
57 }
58 } catch (const std::exception &e) {
59 RCLCPP_ERROR(get_logger(), "raw decode failed: %s", e.what());
60 // Do not set the saved flag; wait for the next frame
61 }
62 }
63
64 std::string topic_;
65 bool saved_;
66 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_;
67};
68
69int main(int argc, char **argv) {
70 rclcpp::init(argc, argv);
71 rclcpp::spin(std::make_shared<SaveOneRaw>());
72 return 0;
73}
6.2.14 相机推流示例集
该示例集提供了多种相机数据订阅和处理功能,支持深度相机、双目相机和单目相机的数据流订阅。
这些相机数据订阅 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_image: 深度图像 (sensor_msgs/Image)rgb_image: RGB图像 (sensor_msgs/Image)rgb_image_compressed: 压缩RGB图像 (sensor_msgs/CompressedImage)camera_info: 相机内参 (sensor_msgs/CameraInfo)
1#include <algorithm>
2#include <deque>
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/core.hpp>
16#include <opencv2/imgproc.hpp>
17#include <opencv2/videoio.hpp>
18
19/**
20 * @brief Example of subscribing to multiple topics for the head depth camera
21 *
22 * You can select which topic type to subscribe to via the startup argument
23 * --ros-args -p topic_type:=<type>:
24 * - depth_image: Depth image (sensor_msgs/Image)
25 * - rgb_image: RGB image (sensor_msgs/Image)
26 * - rgb_image_compressed: RGB compressed image (sensor_msgs/CompressedImage)
27 * - rgb_camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
28 * - depth_camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
29 *
30 * Examples:
31 * ros2 run examples echo_camera_rgbd --ros-args -p
32 * topic_type:=rgb_image ros2
33 * run examples echo_camera_rgbd --ros-args -p
34 * topic_type:=rgb_camera_info
35 *
36 * topic_type defaults to "rgb_image"
37 *
38 * See individual callbacks for more detailed comments
39 */
40class CameraTopicEcho : public rclcpp::Node {
41public:
42 CameraTopicEcho() : Node("camera_topic_echo") {
43 // Select which topic type to subscribe to
44 topic_type_ = declare_parameter<std::string>("topic_type", "rgb_image");
45 dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
46 dump_video_fps_ = declare_parameter<double>("dump_video_fps", 30.0);
47
48 // Subscribed topics and their message layouts
49 // 1. /aima/hal/sensor/rgbd_head_front/depth_image
50 // - topic_type: depth_image
51 // - message type: sensor_msgs::msg::Image
52 // - frame_id: rgbd_head_front
53 // - contents: depth image
54 // 2. /aima/hal/sensor/rgbd_head_front/rgb_image
55 // - topic_type: rgb_image
56 // - message type: sensor_msgs::msg::Image
57 // - frame_id: rgbd_head_front
58 // - contents: RGB image
59 // 3. /aima/hal/sensor/rgbd_head_front/rgb_image/compressed
60 // - topic_type: rgb_image_compressed
61 // - message type: sensor_msgs::msg::CompressedImage
62 // - frame_id: rgbd_head_front
63 // - contents: RGB compressed image
64 // 4. /aima/hal/sensor/rgbd_head_front/rgb_camera_info
65 // - topic_type: camera_info
66 // - message type: sensor_msgs::msg::CameraInfo
67 // - frame_id: rgbd_head_front
68 // - contents: RGB camera intrinsic parameters
69 // 5. /aima/hal/sensor/rgbd_head_front/depth_camera_info
70 // - topic_type: camera_info
71 // - message type: sensor_msgs::msg::CameraInfo
72 // - frame_id: rgbd_head_front
73 // - contents: RGB camera intrinsic parameters
74
75 auto qos = rclcpp::SensorDataQoS();
76
77 // Enable depth image subscription
78 if (topic_type_ == "depth_image") {
79 topic_name_ = "/aima/hal/sensor/rgbd_head_front/depth_image";
80 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
81 topic_name_, qos,
82 std::bind(&CameraTopicEcho::cb_image, this, std::placeholders::_1));
83 RCLCPP_INFO(get_logger(), "✅ Subscribing Depth Image: %s",
84 topic_name_.c_str());
85
86 // Enable RGB image subscription
87 } else if (topic_type_ == "rgb_image") {
88 topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_image";
89 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
90 topic_name_, qos,
91 std::bind(&CameraTopicEcho::cb_image, this, std::placeholders::_1));
92 RCLCPP_INFO(get_logger(), "✅ Subscribing RGB Image: %s",
93 topic_name_.c_str());
94 if (!dump_video_path_.empty()) {
95 RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
96 dump_video_path_.c_str());
97 }
98
99 // Enable RGB compressed image subscription
100 } else if (topic_type_ == "rgb_image_compressed") {
101 topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_image/compressed";
102 sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
103 topic_name_, qos,
104 std::bind(&CameraTopicEcho::cb_compressed, this,
105 std::placeholders::_1));
106 RCLCPP_INFO(get_logger(), "✅ Subscribing CompressedImage: %s",
107 topic_name_.c_str());
108
109 // Enable rgb camera info subscription
110 } else if (topic_type_ == "rgb_camera_info") {
111 topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_camera_info";
112 // RGB-D CameraInfo subscriptions are different from other cameras.
113 // The messages arrive at about 30Hz and SensorDataQoS is sufficient.
114 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
115 topic_name_, qos,
116 std::bind(&CameraTopicEcho::cb_camerainfo, this,
117 std::placeholders::_1));
118 RCLCPP_INFO(get_logger(), "✅ Subscribing RGB CameraInfo: %s",
119 topic_name_.c_str());
120
121 // Enable depth camera info subscription
122 } else if (topic_type_ == "depth_camera_info") {
123 topic_name_ = "/aima/hal/sensor/rgbd_head_front/depth_camera_info";
124 // RGB-D CameraInfo subscriptions are different from other cameras.
125 // The messages arrive at about 30Hz and SensorDataQoS is sufficient.
126 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
127 topic_name_, qos,
128 std::bind(&CameraTopicEcho::cb_camerainfo, this,
129 std::placeholders::_1));
130 RCLCPP_INFO(get_logger(), "✅ Subscribing Depth CameraInfo: %s",
131 topic_name_.c_str());
132
133 // Unknown topic_type error
134 } else {
135 RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
136 throw std::runtime_error("Unknown topic_type");
137 }
138 }
139
140 ~CameraTopicEcho() override {
141 if (video_writer_.isOpened()) {
142 video_writer_.release();
143 RCLCPP_INFO(get_logger(), "Video file closed.");
144 }
145 }
146
147private:
148 // Image callback (depth/RGB image)
149 void cb_image(const sensor_msgs::msg::Image::SharedPtr msg) {
150 update_arrivals();
151
152 if (should_print()) {
153 RCLCPP_INFO(get_logger(),
154 "📸 %s received\n"
155 " • frame_id: %s\n"
156 " • stamp (sec): %.6f\n"
157 " • encoding: %s\n"
158 " • size (WxH): %u x %u\n"
159 " • step (bytes/row):%u\n"
160 " • is_bigendian: %u\n"
161 " • recv FPS (1s): %.1f",
162 topic_type_.c_str(), msg->header.frame_id.c_str(),
163 rclcpp::Time(msg->header.stamp).seconds(),
164 msg->encoding.c_str(), msg->width, msg->height, msg->step,
165 msg->is_bigendian, get_fps());
166 }
167
168 // Video dump is supported only for RGB images
169 if (topic_type_ == "rgb_image" && !dump_video_path_.empty()) {
170 dump_image_to_video(msg);
171 }
172 }
173
174 // CompressedImage callback
175 void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
176 update_arrivals();
177
178 if (should_print()) {
179 RCLCPP_INFO(get_logger(),
180 "🗜️ CompressedImage received\n"
181 " • frame_id: %s\n"
182 " • stamp (sec): %.6f\n"
183 " • format: %s\n"
184 " • data size: %zu\n"
185 " • recv FPS (1s): %.1f",
186 msg->header.frame_id.c_str(),
187 rclcpp::Time(msg->header.stamp).seconds(),
188 msg->format.c_str(), msg->data.size(), get_fps());
189 }
190 }
191
192 // CameraInfo callback (camera intrinsic parameters)
193 void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
194 // CameraInfo is typically published once; print it once
195 std::ostringstream oss;
196 oss << "📷 " << topic_type_ << " received\n"
197 << " • frame_id: " << msg->header.frame_id << "\n"
198 << " • stamp (sec): " << rclcpp::Time(msg->header.stamp).seconds()
199 << "\n"
200 << " • width x height: " << msg->width << " x " << msg->height << "\n"
201 << " • distortion_model:" << msg->distortion_model << "\n"
202 << " • D: [";
203 for (size_t i = 0; i < msg->d.size(); ++i) {
204 oss << msg->d[i];
205 if (i + 1 < msg->d.size())
206 oss << ", ";
207 }
208 oss << "]\n • K: [";
209 for (int i = 0; i < 9; ++i) {
210 oss << msg->k[i];
211 if (i + 1 < 9)
212 oss << ", ";
213 }
214 oss << "]\n • R: [";
215 for (int i = 0; i < 9; ++i) {
216 oss << msg->r[i];
217 if (i + 1 < 9)
218 oss << ", ";
219 }
220 oss << "]\n • P: [";
221 for (int i = 0; i < 12; ++i) {
222 oss << msg->p[i];
223 if (i + 1 < 12)
224 oss << ", ";
225 }
226 oss << "]\n"
227 << " • binning_x: " << msg->binning_x << "\n"
228 << " • binning_y: " << msg->binning_y << "\n"
229 << " • roi: { x_offset: " << msg->roi.x_offset
230 << ", y_offset: " << msg->roi.y_offset
231 << ", height: " << msg->roi.height << ", width: " << msg->roi.width
232 << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
233 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
234 }
235
236 // Track arrival timestamps to compute FPS
237 void update_arrivals() {
238 const rclcpp::Time now = this->get_clock()->now();
239 arrivals_.push_back(now);
240 while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
241 arrivals_.pop_front();
242 }
243 }
244 double get_fps() const { return static_cast<double>(arrivals_.size()); }
245
246 // Control printing frequency
247 bool should_print() {
248 const rclcpp::Time now = this->get_clock()->now();
249 if ((now - last_print_).seconds() >= 1.0) {
250 last_print_ = now;
251 return true;
252 }
253 return false;
254 }
255
256 // Dump received images to a video file (RGB images only)
257 void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
258 cv::Mat image;
259 try {
260 // Obtain the Mat without copying by not specifying encoding
261 cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
262 image = cvp->image;
263 // Convert to BGR for uniform saving
264 if (msg->encoding == "rgb8") {
265 cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
266 } else {
267 RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
268 msg->encoding.c_str());
269 return;
270 }
271 } catch (const std::exception &e) {
272 RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
273 return;
274 }
275
276 // Initialize VideoWriter
277 if (!video_writer_.isOpened()) {
278 int fourcc = cv::VideoWriter::fourcc('m', 'p', '4', 'v');
279 double fps = dump_video_fps_;
280 bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
281 cv::Size(image.cols, image.rows), true);
282 if (!ok) {
283 RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
284 dump_video_path_.c_str());
285 dump_video_path_.clear(); // stop trying
286 return;
287 }
288 RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
289 dump_video_path_.c_str(), image.cols, image.rows, fps);
290 }
291 video_writer_.write(image);
292 }
293
294 // Member variables
295 std::string topic_type_;
296 std::string topic_name_;
297 std::string dump_video_path_;
298 double dump_video_fps_ = 30.0;
299
300 // Subscriptions
301 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
302 rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
303 sub_compressed_;
304 rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
305
306 // FPS statistics
307 rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
308 std::deque<rclcpp::Time> arrivals_;
309
310 // Video writer
311 cv::VideoWriter video_writer_;
312};
313
314int main(int argc, char **argv) {
315 rclcpp::init(argc, argv);
316 auto node = std::make_shared<CameraTopicEcho>();
317 rclcpp::spin(node);
318 rclcpp::shutdown();
319 return 0;
320}
使用说明:
订阅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.mp4
双目相机数据订阅
该示例中用到了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 <algorithm>
2#include <deque>
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/core.hpp>
16#include <opencv2/imgproc.hpp>
17#include <opencv2/videoio.hpp>
18
19/**
20 * @brief Example of subscribing to multiple topics for the stereo head camera
21 *
22 * You can select which topic type to subscribe to via the startup argument
23 * --ros-args -p topic_type:=<type>:
24 * - left_rgb_image: left camera RGB image (sensor_msgs/Image)
25 * - left_rgb_image_compressed: left camera RGB compressed image
26 * (sensor_msgs/CompressedImage)
27 * - left_camera_info: left camera intrinsic parameters
28 * (sensor_msgs/CameraInfo)
29 * - right_rgb_image: right camera RGB image (sensor_msgs/Image)
30 * - right_rgb_image_compressed: right camera RGB compressed image
31 * (sensor_msgs/CompressedImage)
32 * - right_camera_info: right camera intrinsic parameters
33 * (sensor_msgs/CameraInfo)
34 *
35 * Examples:
36 * ros2 run examples echo_camera_stereo --ros-args -p
37 * topic_type:=left_rgb_image ros2 run examples echo_camera_stereo --ros-args -p
38 * topic_type:=right_rgb_image ros2 run examples echo_camera_stereo --ros-args
39 * -p topic_type:=left_camera_info
40 *
41 * topic_type defaults to "left_rgb_image"
42 *
43 * See individual callbacks for more detailed comments
44 */
45class StereoCameraTopicEcho : public rclcpp::Node {
46public:
47 StereoCameraTopicEcho() : Node("stereo_camera_topic_echo") {
48 // Select which topic type to subscribe to
49 topic_type_ =
50 declare_parameter<std::string>("topic_type", "left_rgb_image");
51 dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
52 dump_video_fps_ = declare_parameter<double>("dump_video_fps", 10.0);
53
54 // Subscribed topics and their message layouts
55 // 1. /aima/hal/sensor/stereo_head_front_left/rgb_image
56 // - topic_type: left_rgb_image
57 // - message type: sensor_msgs::msg::Image
58 // - frame_id: stereo_head_front
59 // - child_frame_id: /
60 // - contents: left camera raw image
61 // 2. /aima/hal/sensor/stereo_head_front_left/rgb_image/compressed
62 // - topic_type: left_rgb_image_compressed
63 // - message type: sensor_msgs::msg::CompressedImage
64 // - frame_id: stereo_head_front
65 // - contents: left camera compressed image
66 // 3. /aima/hal/sensor/stereo_head_front_left/camera_info
67 // - topic_type: left_camera_info
68 // - message type: sensor_msgs::msg::CameraInfo
69 // - frame_id: stereo_head_front
70 // - contents: left camera intrinsic parameters
71 // 4. /aima/hal/sensor/stereo_head_front_right/rgb_image
72 // - topic_type: right_rgb_image
73 // - message type: sensor_msgs::msg::Image
74 // - frame_id: stereo_head_front_right
75 // - child_frame_id: /
76 // - contents: right camera raw image
77 // 5. /aima/hal/sensor/stereo_head_front_right/rgb_image/compressed
78 // - topic_type: right_rgb_image_compressed
79 // - message type: sensor_msgs::msg::CompressedImage
80 // - frame_id: stereo_head_front_right
81 // - contents: right camera compressed image
82 // 6. /aima/hal/sensor/stereo_head_front_right/camera_info
83 // - topic_type: right_camera_info
84 // - message type: sensor_msgs::msg::CameraInfo
85 // - frame_id: stereo_head_front_right
86 // - contents: right camera intrinsic parameters
87
88 // Set QoS parameters - use SensorData QoS
89 auto qos = rclcpp::SensorDataQoS();
90
91 // Enable left camera RGB image subscription
92 if (topic_type_ == "left_rgb_image") {
93 topic_name_ = "/aima/hal/sensor/stereo_head_front_left/rgb_image";
94 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
95 topic_name_, qos,
96 std::bind(&StereoCameraTopicEcho::cb_image, this,
97 std::placeholders::_1));
98 RCLCPP_INFO(get_logger(), "✅ Subscribing Left RGB Image: %s",
99 topic_name_.c_str());
100 if (!dump_video_path_.empty()) {
101 RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
102 dump_video_path_.c_str());
103 }
104
105 // Enable left camera RGB compressed image subscription
106 } else if (topic_type_ == "left_rgb_image_compressed") {
107 topic_name_ =
108 "/aima/hal/sensor/stereo_head_front_left/rgb_image/compressed";
109 sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
110 topic_name_, qos,
111 std::bind(&StereoCameraTopicEcho::cb_compressed, this,
112 std::placeholders::_1));
113 RCLCPP_INFO(get_logger(), "✅ Subscribing Left CompressedImage: %s",
114 topic_name_.c_str());
115
116 // Enable left camera info subscription
117 } else if (topic_type_ == "left_camera_info") {
118
119 topic_name_ = "/aima/hal/sensor/stereo_head_front_left/camera_info";
120 // CameraInfo subscriptions must use reliable + transient_local
121 // QoS in order to receive latched/history messages (even if only one
122 // message was published). Here we use keep_last(1) + reliable
123 // + transient_local.
124 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
125 topic_name_,
126 rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
127 std::bind(&StereoCameraTopicEcho::cb_camerainfo, this,
128 std::placeholders::_1));
129 RCLCPP_INFO(get_logger(),
130 "✅ Subscribing Left CameraInfo (with transient_local): %s",
131 topic_name_.c_str());
132
133 // Enable right camera RGB image subscription
134 } else if (topic_type_ == "right_rgb_image") {
135 topic_name_ = "/aima/hal/sensor/stereo_head_front_right/rgb_image";
136 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
137 topic_name_, qos,
138 std::bind(&StereoCameraTopicEcho::cb_image, this,
139 std::placeholders::_1));
140 RCLCPP_INFO(get_logger(), "✅ Subscribing Right RGB Image: %s",
141 topic_name_.c_str());
142 if (!dump_video_path_.empty()) {
143 RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
144 dump_video_path_.c_str());
145 }
146
147 // Enable right camera RGB compressed image subscription
148 } else if (topic_type_ == "right_rgb_image_compressed") {
149 topic_name_ =
150 "/aima/hal/sensor/stereo_head_front_right/rgb_image/compressed";
151 sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
152 topic_name_, qos,
153 std::bind(&StereoCameraTopicEcho::cb_compressed, this,
154 std::placeholders::_1));
155 RCLCPP_INFO(get_logger(), "✅ Subscribing Right CompressedImage: %s",
156 topic_name_.c_str());
157
158 // Enable right camera info subscription
159 } else if (topic_type_ == "right_camera_info") {
160 topic_name_ = "/aima/hal/sensor/stereo_head_front_right/camera_info";
161 // CameraInfo subscriptions must use reliable + transient_local
162 // QoS in order to receive latched/history messages (even if only one
163 // message was published). Here we use keep_last(1) + reliable
164 // + transient_local.
165 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
166 topic_name_,
167 rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
168 std::bind(&StereoCameraTopicEcho::cb_camerainfo, this,
169 std::placeholders::_1));
170 RCLCPP_INFO(get_logger(),
171 "✅ Subscribing Right CameraInfo (with transient_local): %s",
172 topic_name_.c_str());
173
174 // Unknown topic_type error
175 } else {
176 RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
177 throw std::runtime_error("Unknown topic_type");
178 }
179 }
180
181 ~StereoCameraTopicEcho() override {
182 if (video_writer_.isOpened()) {
183 video_writer_.release();
184 RCLCPP_INFO(get_logger(), "Video file closed.");
185 }
186 }
187
188private:
189 // Image callback (left/right 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_ == "left_rgb_image" || topic_type_ == "right_rgb_image") &&
211 !dump_video_path_.empty()) {
212 dump_image_to_video(msg);
213 }
214 }
215
216 // CompressedImage callback (left/right RGB compressed image)
217 void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
218 update_arrivals();
219
220 if (should_print()) {
221 RCLCPP_INFO(get_logger(),
222 "🗜️ %s received\n"
223 " • frame_id: %s\n"
224 " • stamp (sec): %.6f\n"
225 " • format: %s\n"
226 " • data size: %zu\n"
227 " • recv FPS (1s): %.1f",
228 topic_type_.c_str(), msg->header.frame_id.c_str(),
229 rclcpp::Time(msg->header.stamp).seconds(),
230 msg->format.c_str(), msg->data.size(), get_fps());
231 }
232 }
233
234 // CameraInfo callback (left/right camera intrinsic parameters)
235 void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
236 // CameraInfo is typically published once; print it once
237 std::ostringstream oss;
238 oss << "📷 " << topic_type_ << " received\n"
239 << " • frame_id: " << msg->header.frame_id << "\n"
240 << " • stamp (sec): " << rclcpp::Time(msg->header.stamp).seconds()
241 << "\n"
242 << " • width x height: " << msg->width << " x " << msg->height << "\n"
243 << " • distortion_model:" << msg->distortion_model << "\n"
244 << " • D: [";
245 for (size_t i = 0; i < msg->d.size(); ++i) {
246 oss << msg->d[i];
247 if (i + 1 < msg->d.size())
248 oss << ", ";
249 }
250 oss << "]\n • K: [";
251 for (int i = 0; i < 9; ++i) {
252 oss << msg->k[i];
253 if (i + 1 < 9)
254 oss << ", ";
255 }
256 oss << "]\n • R: [";
257 for (int i = 0; i < 9; ++i) {
258 oss << msg->r[i];
259 if (i + 1 < 9)
260 oss << ", ";
261 }
262 oss << "]\n • P: [";
263 for (int i = 0; i < 12; ++i) {
264 oss << msg->p[i];
265 if (i + 1 < 12)
266 oss << ", ";
267 }
268 oss << "]\n"
269 << " • binning_x: " << msg->binning_x << "\n"
270 << " • binning_y: " << msg->binning_y << "\n"
271 << " • roi: { x_offset: " << msg->roi.x_offset
272 << ", y_offset: " << msg->roi.y_offset
273 << ", height: " << msg->roi.height << ", width: " << msg->roi.width
274 << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
275 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
276 }
277
278 // Track arrival timestamps to compute FPS
279 void update_arrivals() {
280 const rclcpp::Time now = this->get_clock()->now();
281 arrivals_.push_back(now);
282 while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
283 arrivals_.pop_front();
284 }
285 }
286 double get_fps() const { return static_cast<double>(arrivals_.size()); }
287
288 // Control printing frequency
289 bool should_print() {
290 const rclcpp::Time now = this->get_clock()->now();
291 if ((now - last_print_).seconds() >= 1.0) {
292 last_print_ = now;
293 return true;
294 }
295 return false;
296 }
297
298 // Dump received images to a video file (RGB images only)
299 void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
300 cv::Mat image;
301 try {
302 // Obtain the Mat without copying by not specifying encoding
303 cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
304 image = cvp->image;
305 // Convert to BGR for uniform saving
306 if (msg->encoding == "rgb8") {
307 cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
308 } else {
309 RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
310 msg->encoding.c_str());
311 return;
312 }
313 } catch (const std::exception &e) {
314 RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
315 return;
316 }
317
318 // Initialize VideoWriter
319 if (!video_writer_.isOpened()) {
320 int fourcc = cv::VideoWriter::fourcc('m', 'p', '4', 'v');
321 double fps = dump_video_fps_;
322 bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
323 cv::Size(image.cols, image.rows), true);
324 if (!ok) {
325 RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
326 dump_video_path_.c_str());
327 dump_video_path_.clear(); // stop trying
328 return;
329 }
330 RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
331 dump_video_path_.c_str(), image.cols, image.rows, fps);
332 }
333 video_writer_.write(image);
334 }
335
336 // Member variables
337 std::string topic_type_;
338 std::string topic_name_;
339 std::string dump_video_path_;
340 double dump_video_fps_ = 10.0;
341
342 // Subscriptions
343 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
344 rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
345 sub_compressed_;
346 rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
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<StereoCameraTopicEcho>();
359 rclcpp::spin(node);
360 rclcpp::shutdown();
361 return 0;
362}
使用说明:
订阅左相机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.mp4
头部后置单目相机数据订阅
该示例中用到了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 <algorithm>
2#include <deque>
3#include <filesystem>
4#include <fstream>
5#include <memory>
6#include <rclcpp/rclcpp.hpp>
7#include <regex>
8#include <sensor_msgs/msg/camera_info.hpp>
9#include <sensor_msgs/msg/compressed_image.hpp>
10#include <sensor_msgs/msg/image.hpp>
11#include <sstream>
12#include <string>
13#include <vector>
14
15#include <cv_bridge/cv_bridge.h>
16#include <opencv2/core.hpp>
17#include <opencv2/imgcodecs.hpp>
18#include <opencv2/imgproc.hpp>
19#include <opencv2/videoio.hpp>
20
21/**
22 * @brief Example of subscribing to multiple topics for the rear head monocular
23 * camera
24 *
25 * You can select which topic type to subscribe to via the startup argument
26 * --ros-args -p topic_type:=<type>:
27 * - rgb_image: RGB image (sensor_msgs/Image)
28 * - rgb_image_compressed: RGB compressed image (sensor_msgs/CompressedImage)
29 * - camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
30 *
31 * Examples:
32 * ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image
33 * ros2 run examples echo_camera_head_rear --ros-args -p
34 * topic_type:=rgb_image_compressed ros2 run examples echo_camera_head_rear
35 * --ros-args -p topic_type:=camera_info
36 *
37 * topic_type defaults to "rgb_image"
38 *
39 * Note: Some rear cameras are rotated by 90 degrees. This example
40 * automatically detects them and applies rotation compensation
41 * when needed.
42 */
43class HeadRearCameraTopicEcho : public rclcpp::Node {
44public:
45 HeadRearCameraTopicEcho() : Node("head_rear_camera_topic_echo") {
46 topic_type_ = declare_parameter<std::string>("topic_type", "rgb_image");
47 dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
48 dump_video_fps_ = declare_parameter<double>("dump_video_fps", 10.0);
49 with_mask_ = declare_parameter<bool>("with_mask", false);
50 auto_rotate_ = declare_parameter<bool>("auto_rotate", true);
51
52 auto qos = rclcpp::SensorDataQoS();
53
54 if (auto_rotate_) {
55 detect_rotate_from_file();
56 }
57
58 if (with_mask_ && !dump_video_path_.empty()) {
59 std::string mask_file = need_rotation_ ? "rgb_head_rear_mask_t35.png"
60 : "rgb_head_rear_mask.png";
61 auto mask_path =
62 std::filesystem::read_symlink("/proc/self/exe").parent_path() /
63 "data" / mask_file;
64 mask_image_ = cv::imread(mask_path, cv::IMREAD_GRAYSCALE);
65 if (mask_image_.empty()) {
66 RCLCPP_ERROR(get_logger(), "Failed to load mask file from %s",
67 mask_path.c_str());
68 throw std::runtime_error("Failed to load mask file");
69 }
70 RCLCPP_INFO(get_logger(), "🎭 Loaded mask: %s", mask_file.c_str());
71 }
72
73 if (topic_type_ == "rgb_image") {
74 topic_name_ = "/aima/hal/sensor/rgb_head_rear/rgb_image";
75 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
76 topic_name_, qos,
77 std::bind(&HeadRearCameraTopicEcho::cb_image, this,
78 std::placeholders::_1));
79 RCLCPP_INFO(get_logger(), "✅ Subscribing RGB Image: %s",
80 topic_name_.c_str());
81 if (!dump_video_path_.empty()) {
82 RCLCPP_INFO(get_logger(),
83 "📝 Will dump received images %s mask %s to video: %s",
84 (with_mask_ ? "with" : "without"),
85 (need_rotation_ ? "with rotation compensation"
86 : "without rotation"),
87 dump_video_path_.c_str());
88 }
89 }
90
91 else if (topic_type_ == "rgb_image_compressed") {
92 topic_name_ = "/aima/hal/sensor/rgb_head_rear/rgb_image/compressed";
93 sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
94 topic_name_, qos,
95 std::bind(&HeadRearCameraTopicEcho::cb_compressed, this,
96 std::placeholders::_1));
97 RCLCPP_INFO(get_logger(), "✅ Subscribing CompressedImage: %s",
98 topic_name_.c_str());
99
100 } else if (topic_type_ == "camera_info") {
101 topic_name_ = "/aima/hal/sensor/rgb_head_rear/camera_info";
102 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
103 topic_name_,
104 rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
105 std::bind(&HeadRearCameraTopicEcho::cb_camerainfo, this,
106 std::placeholders::_1));
107 RCLCPP_INFO(get_logger(),
108 "✅ Subscribing CameraInfo (with transient_local): %s",
109 topic_name_.c_str());
110
111 } else {
112 RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
113 throw std::runtime_error("Unknown topic_type");
114 }
115 }
116
117 ~HeadRearCameraTopicEcho() override {
118 if (video_writer_.isOpened()) {
119 video_writer_.release();
120 RCLCPP_INFO(get_logger(), "Video file closed.");
121 }
122 }
123
124private:
125 void detect_rotate_from_file() {
126 std::string hw_file = "/agibot/data/info/hardware_version";
127 std::ifstream file(hw_file);
128
129 if (file.is_open()) {
130 std::getline(file, hw_version_);
131 hw_version_.erase(hw_version_.find_last_not_of(" \n\r\t") + 1);
132 RCLCPP_INFO(get_logger(), "📋 Hardware version from file: '%s'",
133 hw_version_.c_str());
134 check_rotation(hw_version_);
135 } else {
136 RCLCPP_WARN(get_logger(), "⚠️ Hardware version file not found: %s",
137 hw_file.c_str());
138 }
139 }
140
141 void check_rotation(const std::string &version) {
142 std::regex rear_rotation_pattern("t3[d.]5", std::regex::icase);
143 if (std::regex_search(version, rear_rotation_pattern)) {
144 need_rotation_ = true;
145 RCLCPP_INFO(get_logger(), "🔄 Rotation detected: Rear camera rotation "
146 "compensation enabled (90° counter-clockwise)");
147 } else {
148 RCLCPP_INFO(
149 get_logger(),
150 "✅ No rotation detected (%s): No rotation compensation needed",
151 version.c_str());
152 }
153 }
154
155 cv::Mat rotate_image_90_ccw(const cv::Mat &image) {
156 cv::Mat rotated;
157 cv::rotate(image, rotated, cv::ROTATE_90_COUNTERCLOCKWISE);
158 return rotated;
159 }
160
161 void cb_image(const sensor_msgs::msg::Image::SharedPtr msg) {
162 update_arrivals();
163
164 if (should_print()) {
165 std::string rotation_note = need_rotation_ ? " (rotated 90° CCW)" : "";
166 RCLCPP_INFO(get_logger(),
167 "📸 %s received%s\n"
168 " • frame_id: %s\n"
169 " • stamp (sec): %.6f\n"
170 " • encoding: %s\n"
171 " • size (WxH): %u x %u\n"
172 " • step (bytes/row):%u\n"
173 " • is_bigendian: %u\n"
174 " • recv FPS (1s): %.1f",
175 topic_type_.c_str(), rotation_note.c_str(),
176 msg->header.frame_id.c_str(),
177 rclcpp::Time(msg->header.stamp).seconds(),
178 msg->encoding.c_str(), msg->width, msg->height, msg->step,
179 msg->is_bigendian, get_fps());
180 }
181
182 if (topic_type_ == "rgb_image" && !dump_video_path_.empty()) {
183 dump_image_to_video(msg);
184 }
185 }
186
187 void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
188 update_arrivals();
189
190 if (should_print()) {
191 RCLCPP_INFO(get_logger(),
192 "🗜️ %s received\n"
193 " • frame_id: %s\n"
194 " • stamp (sec): %.6f\n"
195 " • format: %s\n"
196 " • data size: %zu\n"
197 " • recv FPS (1s): %.1f",
198 topic_type_.c_str(), msg->header.frame_id.c_str(),
199 rclcpp::Time(msg->header.stamp).seconds(),
200 msg->format.c_str(), msg->data.size(), get_fps());
201 }
202 }
203
204 void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
205 std::ostringstream oss;
206 oss << "📷 " << topic_type_ << " received\n"
207 << " • frame_id: " << msg->header.frame_id << "\n"
208 << " • stamp (sec): " << rclcpp::Time(msg->header.stamp).seconds()
209 << "\n"
210 << " • width x height: " << msg->width << " x " << msg->height << "\n"
211 << " • distortion_model:" << msg->distortion_model << "\n"
212 << " • D: [";
213 for (size_t i = 0; i < msg->d.size(); ++i) {
214 oss << msg->d[i];
215 if (i + 1 < msg->d.size())
216 oss << ", ";
217 }
218 oss << "]\n • K: [";
219 for (int i = 0; i < 9; ++i) {
220 oss << msg->k[i];
221 if (i + 1 < 9)
222 oss << ", ";
223 }
224 oss << "]\n • R: [";
225 for (int i = 0; i < 9; ++i) {
226 oss << msg->r[i];
227 if (i + 1 < 9)
228 oss << ", ";
229 }
230 oss << "]\n • P: [";
231 for (int i = 0; i < 12; ++i) {
232 oss << msg->p[i];
233 if (i + 1 < 12)
234 oss << ", ";
235 }
236 oss << "]\n"
237 << " • binning_x: " << msg->binning_x << "\n"
238 << " • binning_y: " << msg->binning_y << "\n"
239 << " • roi: { x_offset: " << msg->roi.x_offset
240 << ", y_offset: " << msg->roi.y_offset
241 << ", height: " << msg->roi.height << ", width: " << msg->roi.width
242 << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
243 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
244 }
245
246 void update_arrivals() {
247 const rclcpp::Time now = this->get_clock()->now();
248 arrivals_.push_back(now);
249 while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
250 arrivals_.pop_front();
251 }
252 }
253 double get_fps() const { return static_cast<double>(arrivals_.size()); }
254
255 bool should_print() {
256 const rclcpp::Time now = this->get_clock()->now();
257 if ((now - last_print_).seconds() >= 1.0) {
258 last_print_ = now;
259 return true;
260 }
261 return false;
262 }
263
264 void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
265 cv::Mat image;
266 try {
267 cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
268 image = cvp->image;
269 if (msg->encoding == "rgb8") {
270 cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
271 } else {
272 RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
273 msg->encoding.c_str());
274 return;
275 }
276
277 if (need_rotation_) {
278 image = rotate_image_90_ccw(image);
279 }
280
281 if (with_mask_) {
282 image.setTo(cv::Scalar(0, 0, 0), mask_image_ == 0);
283 }
284 } catch (const std::exception &e) {
285 RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
286 return;
287 }
288
289 if (!video_writer_.isOpened()) {
290 int fourcc = cv::VideoWriter::fourcc('m', 'p', '4', 'v');
291 double fps = dump_video_fps_;
292 bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
293 cv::Size(image.cols, image.rows), true);
294 if (!ok) {
295 RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
296 dump_video_path_.c_str());
297 dump_video_path_.clear();
298 return;
299 }
300 RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
301 dump_video_path_.c_str(), image.cols, image.rows, fps);
302 }
303 video_writer_.write(image);
304 }
305
306 std::string topic_type_;
307 std::string topic_name_;
308 std::string dump_video_path_;
309 double dump_video_fps_ = 10.0;
310 bool with_mask_;
311 bool auto_rotate_;
312 bool need_rotation_ = false;
313 std::string hw_version_;
314 cv::Mat mask_image_;
315
316 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
317 rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
318 sub_compressed_;
319 rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
320
321 rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
322 std::deque<rclcpp::Time> arrivals_;
323
324 cv::VideoWriter video_writer_;
325};
326
327int main(int argc, char **argv) {
328 rclcpp::init(argc, argv);
329 auto node = std::make_shared<HeadRearCameraTopicEcho>();
330 rclcpp::spin(node);
331 rclcpp::shutdown();
332 return 0;
333}
使用说明:
订阅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.mp4
录制视频并对把手遮挡区域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.mp4
禁用自动旋转补偿(获取原始图像):
ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image -p auto_rotate:=false
应用场景:
人脸识别和追踪
目标检测和识别
视觉SLAM
图像处理和计算机视觉算法开发
机器人视觉导航
6.2.15 头部触摸传感器数据订阅
该示例中用到了 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#include <string>
8
9class TouchStateSubscriber : public rclcpp::Node {
10public:
11 TouchStateSubscriber() : Node("touch_state_subscriber") {
12 subscription_ = this->create_subscription<aimdk_msgs::msg::TouchState>(
13 "/aima/hal/sensor/touch_head", 10,
14 std::bind(&TouchStateSubscriber::touch_callback, this,
15 std::placeholders::_1));
16
17 RCLCPP_INFO(this->get_logger(), "TouchState subscriber started, listening "
18 "to /aima/hal/sensor/touch_head");
19 }
20
21private:
22 void touch_callback(const aimdk_msgs::msg::TouchState::SharedPtr msg) {
23 // print message info
24 std::string event_str = get_event_type_string(msg->event_type);
25 RCLCPP_INFO(this->get_logger(), "Timestamp: %d.%09d, Event: %s (%d)",
26 msg->header.stamp.sec, msg->header.stamp.nanosec,
27 event_str.c_str(), msg->event_type);
28 }
29
30 std::string get_event_type_string(uint8_t event_type) {
31 switch (event_type) {
32 case aimdk_msgs::msg::TouchState::UNKNOWN:
33 return "UNKNOWN";
34 case aimdk_msgs::msg::TouchState::IDLE:
35 return "IDLE";
36 case aimdk_msgs::msg::TouchState::TOUCH:
37 return "TOUCH";
38 default:
39 return "INVALID";
40 }
41 }
42 rclcpp::Subscription<aimdk_msgs::msg::TouchState>::SharedPtr subscription_;
43};
44
45int main(int argc, char *argv[]) {
46 rclcpp::init(argc, argv);
47 auto node = std::make_shared<TouchStateSubscriber>();
48 rclcpp::spin(node);
49 rclcpp::shutdown();
50 return 0;
51}
使用说明:
ros2 run examples echo_head_touch_sensor
输出示例:
...
[INFO] [1779174857.614539691] [touch_state_subscriber]: Timestamp: 1779174857.613006485, Event: IDLE (1)
[INFO] [1779174857.623661452] [touch_state_subscriber]: Timestamp: 1779174857.623052293, Event: TOUCH (2)
6.2.16 激光雷达数据订阅
该示例中用到了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 // SensorDataQoS: BEST_EFFORT + VOLATILE
37 auto qos = rclcpp::SensorDataQoS();
38
39 if (topic_type_ == "pointcloud") {
40 topic_name_ = "/aima/hal/sensor/lidar_chest_front/lidar_pointcloud";
41 sub_pointcloud_ = create_subscription<sensor_msgs::msg::PointCloud2>(
42 topic_name_, qos,
43 std::bind(&LidarChestEcho::cb_pointcloud, this,
44 std::placeholders::_1));
45 RCLCPP_INFO(get_logger(), "✅ Subscribing LIDAR PointCloud2: %s",
46 topic_name_.c_str());
47 } else if (topic_type_ == "imu") {
48 topic_name_ = "/aima/hal/sensor/lidar_chest_front/imu";
49 sub_imu_ = create_subscription<sensor_msgs::msg::Imu>(
50 topic_name_, qos,
51 std::bind(&LidarChestEcho::cb_imu, this, std::placeholders::_1));
52 RCLCPP_INFO(get_logger(), "✅ Subscribing LIDAR IMU: %s",
53 topic_name_.c_str());
54 } else {
55 RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
56 throw std::runtime_error("Unknown topic_type");
57 }
58 }
59
60private:
61 // PointCloud2 callback
62 void cb_pointcloud(const sensor_msgs::msg::PointCloud2::SharedPtr msg) {
63 update_arrivals();
64
65 if (should_print()) {
66 std::ostringstream oss;
67 oss << std::fixed << std::boolalpha << std::setfill('0');
68 oss << "🟢 LIDAR PointCloud2 received\n"
69 << " • frame_id: " << msg->header.frame_id << "\n"
70 << " • stamp (sec): " << msg->header.stamp.sec << "."
71 << std::setw(9) << msg->header.stamp.nanosec << "\n"
72 << " • width x height: " << msg->width << " x " << msg->height
73 << "\n"
74 << " • point_step: " << msg->point_step << "\n"
75 << " • row_step: " << msg->row_step << "\n"
76 << " • fields: ";
77 for (const auto &f : msg->fields)
78 oss << f.name << "(" << (int)f.datatype << ") ";
79 oss << "\n • is_bigendian: " << msg->is_bigendian
80 << "\n • is_dense: " << msg->is_dense
81 << "\n • data size: " << msg->data.size()
82 << std::setprecision(1) // fps precision .1f
83 << "\n • recv FPS (1s): " << get_fps();
84 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
85 }
86 }
87
88 // IMU callback
89 void cb_imu(const sensor_msgs::msg::Imu::SharedPtr msg) {
90 update_arrivals();
91
92 if (should_print()) {
93 std::ostringstream oss;
94 oss << std::fixed << std::setfill('0');
95 oss << "🟢 LIDAR IMU received\n"
96 << " • frame_id: " << msg->header.frame_id << "\n"
97 << " • stamp (sec): " << msg->header.stamp.sec << "."
98 << std::setw(9) << msg->header.stamp.nanosec << "\n"
99 << std::setprecision(6) // data precision .6f
100 << " • orientation: [" << msg->orientation.x << ", "
101 << msg->orientation.y << ", " << msg->orientation.z << ", "
102 << msg->orientation.w << "]\n"
103 << " • angular_velocity:[" << msg->angular_velocity.x << ", "
104 << msg->angular_velocity.y << ", " << msg->angular_velocity.z << "]\n"
105 << " • linear_accel: [" << msg->linear_acceleration.x << ", "
106 << msg->linear_acceleration.y << ", " << msg->linear_acceleration.z
107 << "]\n"
108 << std::setprecision(1) // fps precision .1f
109 << " • recv FPS (1s): " << get_fps();
110 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
111 }
112 }
113
114 // Update FPS statistics
115 void update_arrivals() {
116 const rclcpp::Time now = this->get_clock()->now();
117 arrivals_.push_back(now);
118 while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
119 arrivals_.pop_front();
120 }
121 }
122 double get_fps() const { return static_cast<double>(arrivals_.size()); }
123
124 // Control print frequency
125 bool should_print() {
126 const rclcpp::Time now = this->get_clock()->now();
127 if ((now - last_print_).seconds() >= 1.0) {
128 last_print_ = now;
129 return true;
130 }
131 return false;
132 }
133
134 // Member variables
135 std::string topic_type_;
136 std::string topic_name_;
137
138 rclcpp::Subscription<sensor_msgs::msg::PointCloud2>::SharedPtr
139 sub_pointcloud_;
140 rclcpp::Subscription<sensor_msgs::msg::Imu>::SharedPtr sub_imu_;
141
142 rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
143 std::deque<rclcpp::Time> arrivals_;
144};
145
146int main(int argc, char **argv) {
147 rclcpp::init(argc, argv);
148 auto node = std::make_shared<LidarChestEcho>();
149 rclcpp::spin(node);
150 rclcpp::shutdown();
151 return 0;
152}
使用说明:
# 订阅激光雷达点云数据
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] [1779175640.420801950] [lidar_chest_echo]: 🟢 LIDAR PointCloud2 received
• frame_id: lidar_chest_front
• stamp (sec): 1779175640.315327000
• width x height: 24779 x 1
• point_step: 32
• row_step: 792928
• fields: x(7) y(7) z(7) intensity(7) ring(4) timestamp(8)
• is_bigendian: false
• is_dense: true
• data size: 792928
• recv FPS (1s): 11.0
6.2.17 IMU数据订阅
该示例中用到了echo_imu_data,通过订阅IMU话题接收机器人的姿态、角速度与线性加速度数据,并打印各字段的协方差对角线信息。默认订阅胸部IMU,可通过参数切换到胯部IMU、Lidar IMU或深度相机IMU。
功能特点:
订阅
sensor_msgs/Imu数据,输出姿态四元数、角速度、线性加速度打印姿态/角速度/加速度的协方差对角线(
-1表示未知)统计接收间隔(
recv_dt),便于观察实际到达频率通过
imu_topic参数可切换不同 IMU 源
支持的话题:
/aima/hal/imu/chest/state:胸部IMU(默认)/aima/hal/imu/torso/state:胯部IMU/aima/hal/sensor/lidar_chest_front/imu:Lidar IMU/aima/hal/sensor/rgbd_head_front/imu:深度相机 IMU
技术实现:
使用
SensorDataQoS(BEST_EFFORT+VOLATILE),与传感器数据默认 QoS 一致
1#include <chrono>
2#include <iomanip>
3#include <rclcpp/rclcpp.hpp>
4#include <sensor_msgs/msg/imu.hpp>
5#include <string>
6
7class ImuPrinter : public rclcpp::Node {
8public:
9 ImuPrinter() : rclcpp::Node("imu_printer") {
10 // Allow overriding the topic name via a parameter
11 imu_topic_ = this->declare_parameter<std::string>(
12 "imu_topic",
13 "/aima/hal/imu/chest/state"); // Change to other IMU topics, e.g.
14 // waist IMU: /aima/hal/imu/torso/state
15 // lidar IMU:
16 // /aima/hal/sensor/lidar_chest_front/imu
17
18 auto qos = rclcpp::SensorDataQoS(); // BestEffort / Volatile
19 sub_ = this->create_subscription<sensor_msgs::msg::Imu>(
20 imu_topic_, qos,
21 std::bind(&ImuPrinter::cb, this, std::placeholders::_1));
22
23 RCLCPP_INFO(this->get_logger(), "Subscribing IMU: %s", imu_topic_.c_str());
24 }
25
26private:
27 void cb(const sensor_msgs::msg::Imu::SharedPtr msg) {
28 // Compute receive interval (based on wall time, for observation only)
29 auto now = std::chrono::steady_clock::now();
30 double dt_ms =
31 last_recv_.time_since_epoch().count() == 0
32 ? 0.0
33 : std::chrono::duration<double, std::milli>(now - last_recv_)
34 .count();
35 last_recv_ = now;
36
37 const auto &q = msg->orientation;
38 const auto &w = msg->angular_velocity;
39 const auto &a = msg->linear_acceleration;
40
41 // Print
42 std::ostringstream oss;
43 oss << std::fixed << std::setprecision(6);
44 oss << "stamp=" << rclcpp::Time(msg->header.stamp).seconds()
45 << "s frame=" << msg->header.frame_id
46 << " recv_dt=" << std::setprecision(3) << dt_ms << "ms\n"
47 << std::setprecision(6) << " orientation (x,y,z,w): [" << q.x << ", "
48 << q.y << ", " << q.z << ", " << q.w << "]\n"
49 << " angular_velocity (rad/s): [" << w.x << ", " << w.y << ", " << w.z
50 << "]\n"
51 << " linear_accel (m/s^2): [" << a.x << ", " << a.y << ", " << a.z
52 << "]";
53
54 // Optional: print diagonal covariances (-1 means unknown)
55 auto oc0 = msg->orientation_covariance[0];
56 auto oc4 = msg->orientation_covariance[4];
57 auto oc8 = msg->orientation_covariance[8];
58 auto wc0 = msg->angular_velocity_covariance[0];
59 auto wc4 = msg->angular_velocity_covariance[4];
60 auto wc8 = msg->angular_velocity_covariance[8];
61 auto ac0 = msg->linear_acceleration_covariance[0];
62 auto ac4 = msg->linear_acceleration_covariance[4];
63 auto ac8 = msg->linear_acceleration_covariance[8];
64
65 oss << "\n cov_diag(ori, ang, acc): [" << oc0 << "," << oc4 << "," << oc8
66 << " | " << wc0 << "," << wc4 << "," << wc8 << " | " << ac0 << ","
67 << ac4 << "," << ac8 << "]";
68
69 RCLCPP_INFO(this->get_logger(), "%s", oss.str().c_str());
70 }
71
72 std::string imu_topic_;
73 rclcpp::Subscription<sensor_msgs::msg::Imu>::SharedPtr sub_;
74 std::chrono::steady_clock::time_point last_recv_{};
75};
76
77int main(int argc, char **argv) {
78 rclcpp::init(argc, argv);
79 rclcpp::spin(std::make_shared<ImuPrinter>());
80 rclcpp::shutdown();
81 return 0;
82}
使用说明:
# 订阅胸部IMU(默认)
ros2 run examples echo_imu_data
# 订阅胯部IMU
ros2 run examples echo_imu_data --ros-args -p imu_topic:=/aima/hal/imu/torso/state
# 订阅Lidar IMU
ros2 run examples echo_imu_data --ros-args -p imu_topic:=/aima/hal/sensor/lidar_chest_front/imu
输出示例:
[INFO] [1781764206.320174965] [imu_printer]: Subscribing IMU: /aima/hal/imu/chest/state
[INFO] [1781764206.318604000] [imu_printer]: stamp=1781764206.318604s frame=base_link recv_dt=2.039ms
orientation (x,y,z,w): [0.087036, -0.264735, -0.019103, -0.960195]
angular_velocity (rad/s): [-0.006531, 0.002874, 0.002391]
linear_accel (m/s^2): [-5.013936, -1.548364, 8.280822]
cov_diag(ori, ang, acc): [0.000000,0.000000,0.000000 | 0.000000,0.000000,0.000000 | 0.000000,0.000000,0.000000]
6.2.18 播放视频
该示例中用到了play_video,在运行节点程序前,需要先将视频上传到机器人的**交互计算单元(PC3)**上(用户可在其上创建一个用来存储视频的目录,如/var/tmp/videos/),然后将节点程序中的video_path改为需要播放视频的路径。
注意
⚠️ 请注意!交互计算单元(PC3)独立于二开程序所在的开发计算单元(PC2), 音视频文件务必存入交互计算单元(IP: 10.0.1.42)。
⚠️ 音视频文件夹及该文件夹所有父目录应当为所有用户可访问读取(建议在/var/tmp/下创建子目录存放)
注意
使用本功能前,请在运控计算单元(PC1)上执行以下命令关闭 task_manager 模块,避免其与二开程序争抢显示屏控制权:
aima em stop-app task_manager
功能说明
通过调用PlayVideo服务,可以让机器人在屏幕上播放指定路径的视频文件。请确保视频文件已上传到交互计算单元,否则播放会失败。
💡 提示:视频文件的准备方式
您可以使用 echo_camera_rgbd 示例将机器人的深度相机 RGB 图像录制为
.mp4视频文件,再将其作为play_video的输入:第一步:在机器人开发计算单元(PC2)上录制视频
ros2 run examples echo_camera_rgbd --ros-args -p topic_type:=rgb_image -p dump_video_path:=$PWD/output.mp4 # 录制完成后按 Ctrl+C 停止,视频保存为 output.mp4第二步:将视频传输到交互计算单元(PC3,IP: 10.0.1.42)
scp output.mp4 user@10.0.1.42:/var/tmp/videos/output.mp4第三步:运行 play_video 播放
ros2 run examples play_video # 修改代码中 video_path 为 /var/tmp/videos/output.mp4
1#include "aimdk_msgs/srv/play_video.hpp"
2#include "aimdk_msgs/msg/common_request.hpp"
3#include "rclcpp/rclcpp.hpp"
4#include <algorithm>
5#include <cctype>
6#include <chrono>
7#include <iostream>
8#include <memory>
9#include <signal.h>
10#include <string>
11
12// Global variable used for signal handling
13std::shared_ptr<rclcpp::Node> g_node = nullptr;
14
15// Signal handler function
16void signal_handler(int signal) {
17 if (g_node) {
18 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
19 signal);
20 g_node.reset();
21 }
22 rclcpp::shutdown();
23 exit(signal);
24}
25
26class PlayVideoClient : public rclcpp::Node {
27public:
28 PlayVideoClient() : Node("play_video_client") {
29 client_ = this->create_client<aimdk_msgs::srv::PlayVideo>(
30 "/aimdk_5Fmsgs/srv/PlayVideo");
31 RCLCPP_INFO(this->get_logger(), "✅ PlayVideo client node started.");
32
33 // Wait for the service to become available
34 while (!client_->wait_for_service(std::chrono::seconds(2))) {
35 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
36 }
37 RCLCPP_INFO(this->get_logger(),
38 "🟢 Service available, ready to send request.");
39 }
40
41 bool send_request(const std::string &video_path, uint8_t mode,
42 int32_t priority) {
43 try {
44 auto request = std::make_shared<aimdk_msgs::srv::PlayVideo::Request>();
45
46 request->video_path = video_path;
47 request->mode = mode;
48 request->priority = priority;
49
50 RCLCPP_INFO(this->get_logger(),
51 "📨 Sending request to play video: mode=%hhu video=%s", mode,
52 video_path.c_str());
53
54 const std::chrono::milliseconds timeout(250);
55 for (int i = 0; i < 8; i++) {
56 request->header.header.stamp = this->now();
57 auto future = client_->async_send_request(request);
58 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
59 future, timeout);
60 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
61 // retry as remote peer is NOT handled well by ROS
62 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
63 continue;
64 }
65 // future.done
66 auto response = future.get();
67 if (response->success) {
68 RCLCPP_INFO(this->get_logger(),
69 "✅ Request to play video recorded successfully: %s",
70 response->message.c_str());
71 return true;
72 } else {
73 RCLCPP_ERROR(this->get_logger(),
74 "❌ Failed to record play-video request: %s",
75 response->message.c_str());
76 return false;
77 }
78 }
79 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
80 return false;
81 } catch (const std::exception &e) {
82 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
83 return false;
84 }
85 }
86
87private:
88 rclcpp::Client<aimdk_msgs::srv::PlayVideo>::SharedPtr client_;
89};
90
91int main(int argc, char **argv) {
92 try {
93 rclcpp::init(argc, argv);
94
95 // Set up signal handlers
96 signal(SIGINT, signal_handler);
97 signal(SIGTERM, signal_handler);
98
99 std::string default_video_path =
100 "/agibot/software/face_ui/bin/app/aarch64-generic/assets/mp4/"
101 "emoji_x2_sleepy.mp4";
102 std::string video_path;
103 std::cout << "Enter video path (default: " << default_video_path << "): ";
104 std::getline(std::cin, video_path);
105 // Trim leading/trailing whitespace (parity with Python's .strip())
106 auto not_space = [](int c) { return !std::isspace(c); };
107 video_path.erase(
108 video_path.begin(),
109 std::find_if(video_path.begin(), video_path.end(), not_space));
110 video_path.erase(
111 std::find_if(video_path.rbegin(), video_path.rend(), not_space).base(),
112 video_path.end());
113 if (video_path.empty()) {
114 video_path = default_video_path;
115 }
116 int32_t priority = 5;
117 int mode = 2; // Loop playback
118 std::cout << "Enter video play mode (1: once, 2: loop): ";
119 std::cin >> mode;
120 if (mode < 1 || mode > 2) {
121 RCLCPP_ERROR(rclcpp::get_logger("main"), "Invalid play mode: %d", mode);
122 rclcpp::shutdown();
123 return 1;
124 }
125
126 g_node = std::make_shared<PlayVideoClient>();
127 auto client = std::dynamic_pointer_cast<PlayVideoClient>(g_node);
128
129 if (client) {
130 client->send_request(video_path, mode, priority);
131 }
132
133 // Clean up resources
134 g_node.reset();
135 rclcpp::shutdown();
136
137 return 0;
138 } catch (const std::exception &e) {
139 RCLCPP_ERROR(rclcpp::get_logger("main"),
140 "Program exited with exception: %s", e.what());
141 return 1;
142 }
143}
6.2.19 音频文件播放
该示例中用到了play_audio,通过该节点可实现播放指定的音频文件,支持WAV(PCM编码)、RAW PCM格式的音频文件播放,不支持MP3等其他格式。
功能特点:
支持WAV(PCM编码)、RAW PCM格式播放,不支持MP3等其他格式
支持优先级控制,可设置播放优先级
支持自定义文件路径和播放参数
提供完整的错误处理和状态反馈
技术实现:
使用PlayAudioFile服务进行音频文件播放
支持优先级设置(priority: 1~10,priority_weight: 1~100)
提供详细的播放状态反馈
应用场景:
音频文件播放和媒体控制
语音提示和音效播放
多媒体应用开发
机器人交互音频反馈
注意
⚠️ 请注意!交互计算单元(PC3)独立于二开程序所在的开发计算单元(PC2), 音视频文件务必存入交互计算单元(IP: 10.0.1.42)。
⚠️ 音视频文件夹及该文件夹所有父目录应当为所有用户可访问读取(建议在/var/tmp/下创建子目录存放)
1#include <aimdk_msgs/msg/common_state.hpp>
2#include <aimdk_msgs/srv/play_audio_file.hpp>
3#include <algorithm>
4#include <cctype>
5#include <filesystem>
6#include <iostream>
7#include <rclcpp/rclcpp.hpp>
8#include <string>
9
10using PlayAudioFile = aimdk_msgs::srv::PlayAudioFile;
11
12int main(int argc, char **argv) {
13 rclcpp::init(argc, argv);
14 auto node = rclcpp::Node::make_shared("play_audio_client_min");
15
16 // 1) Service name
17 const std::string service_name = "/aimdk_5Fmsgs/srv/PlayAudioFile";
18 auto client = node->create_client<PlayAudioFile>(service_name);
19
20 RCLCPP_INFO(node->get_logger(), "✅ PlayAudio client node created.");
21
22 // 2) Input file path (prompt user if not provided as argument)
23 std::string default_file = "/agibot/data/var/hal_audio/file/bingo.wav";
24 std::string file_name;
25
26 if (argc > 1) {
27 file_name = argv[1];
28 } else {
29 std::cout << "Enter audio file path to play (default: " << default_file
30 << "): ";
31 if (!std::getline(std::cin, file_name) || !rclcpp::ok()) {
32 std::cout << std::endl;
33 rclcpp::shutdown();
34 return 1;
35 }
36 // Trim leading/trailing whitespace (parity with Python's .strip())
37 auto not_space = [](int c) { return !std::isspace(c); };
38 file_name.erase(
39 file_name.begin(),
40 std::find_if(file_name.begin(), file_name.end(), not_space));
41 file_name.erase(
42 std::find_if(file_name.rbegin(), file_name.rend(), not_space).base(),
43 file_name.end());
44 if (file_name.empty()) {
45 file_name = default_file;
46 }
47 }
48
49 // 3) Build the request
50 // Check if Ctrl+C was pressed during user input
51 if (!rclcpp::ok()) {
52 rclcpp::shutdown();
53 return 1;
54 }
55
56 auto req = std::make_shared<PlayAudioFile::Request>();
57 req->file.pkg_name = "demo_client";
58 req->file.file_name = std::filesystem::path(file_name).filename().string();
59 req->file.file_path =
60 std::filesystem::path(file_name).parent_path().string() + "/";
61 req->file.priority = 8;
62 req->file.priority_weight = 0;
63
64 // 4) Wait for service
65 while (!client->wait_for_service(std::chrono::seconds(2))) {
66 if (!rclcpp::ok()) {
67 RCLCPP_ERROR(node->get_logger(),
68 "Interrupted while waiting for the service. Exiting.");
69 rclcpp::shutdown();
70 return 1;
71 }
72 RCLCPP_INFO(node->get_logger(), "⏳ Service unavailable, waiting...");
73 }
74 RCLCPP_INFO(node->get_logger(),
75 "🟢 Service available, ready to send request.");
76
77 RCLCPP_INFO(node->get_logger(), "📨 Sending request to play audio: %s",
78 file_name.c_str());
79
80 rclcpp::FutureReturnCode rc = rclcpp::FutureReturnCode::TIMEOUT;
81 std::shared_future<PlayAudioFile::Response::SharedPtr> future;
82 for (int i = 0; i < 5; ++i) {
83 req->request.header.stamp = node->now();
84 future = client->async_send_request(req);
85 rc = rclcpp::spin_until_future_complete(node, future,
86 std::chrono::seconds(1));
87 if (rc == rclcpp::FutureReturnCode::SUCCESS) {
88 break;
89 }
90 if (rc == rclcpp::FutureReturnCode::INTERRUPTED) {
91 // Check if Ctrl+C was pressed during spin_until_future_complete
92 if (rclcpp::ok()) {
93 RCLCPP_WARN(node->get_logger(), "Interrupted while waiting");
94 }
95 rclcpp::shutdown();
96 return 1;
97 }
98 // retry as remote peer is NOT handled well by ROS
99 RCLCPP_INFO(node->get_logger(), "trying ... [%d]", i);
100 }
101
102 if (rc != rclcpp::FutureReturnCode::SUCCESS) {
103 // Check if Ctrl+C was pressed, avoid using node after shutdown
104 if (rclcpp::ok()) {
105 RCLCPP_ERROR(node->get_logger(),
106 "❌ Service call not completed or timed out.");
107 }
108 rclcpp::shutdown();
109 return 1;
110 }
111
112 // 5) Handle response (success is in reponse.status)
113 try {
114 const auto resp = future.get();
115 bool success = resp->reponse.status.value ==
116 aimdk_msgs::msg::CommonState::SUCCESS; // SUCCESS (1)
117
118 if (success) {
119 RCLCPP_INFO(node->get_logger(),
120 "✅ Request to play audio file recorded successfully.");
121 } else {
122 // FAILURE(2): focus-apply failed; INVALID(5): invalid params;
123 // other: see CommonState for details.
124 const char *reason = "unknown";
125 switch (resp->reponse.status.value) {
126 case aimdk_msgs::msg::CommonState::FAILURE: // FAILURE (2)
127 reason = "focus apply failed";
128 break;
129 case aimdk_msgs::msg::CommonState::INVALID: // INVALID (5)
130 reason = "invalid params";
131 break;
132 default:
133 break;
134 }
135 RCLCPP_ERROR(node->get_logger(),
136 "❌ Failed to record play-audio request (status=%d, %s): %s",
137 resp->reponse.status.value, reason,
138 resp->reponse.message.c_str());
139 }
140 } catch (const std::exception &e) {
141 RCLCPP_ERROR(node->get_logger(), "Call exception: %s", e.what());
142 }
143
144 rclcpp::shutdown();
145 return 0;
146}
使用说明:
# 播放默认音频文件
ros2 run examples play_audio
# 播放指定音频文件
# 注意替换/path/to/your/audio_file.wav为交互板上实际文件路径
ros2 run examples play_audio /path/to/your/audio_file.wav
# 播放TTS缓存文件
ros2 run examples play_audio /agibot/data/var/hal_audio/file/bingo.wav
输出示例:
[INFO] [1779184008.888874978] [play_audio_client_min]: ✅ PlayAudio client node created.
[INFO] [1779184008.895467698] [play_audio_client_min]: 🟢 Service available, ready to send request.
[INFO] [1779184008.897450608] [play_audio_client_min]: 📨 Sending request to play audio: /agibot/data/var/hal_audio/file/bingo.wav
[INFO] [1779184008.962054291] [play_audio_client_min]: ✅ Request to play audio file recorded successfully.
注意事项:
确保音频文件路径正确且文件存在
支持的文件格式:WAV(PCM编码)、RAW PCM,不支持MP3等其他格式
优先级设置影响播放队列顺序
6.2.20 TTS (文字转语音)
该示例中用到了play_tts,通过该节点可实现语音播放输入的文字,用户可根据不同的场景输入相应的文本。
功能特点:
支持命令行参数和交互式输入
完整的服务可用性检查和错误处理
支持优先级控制和打断机制
提供详细的播放状态反馈
核心代码
1#include <aimdk_msgs/msg/tts_priority_level.hpp>
2#include <aimdk_msgs/srv/play_tts.hpp>
3#include <chrono>
4#include <iostream>
5#include <rclcpp/rclcpp.hpp>
6#include <string>
7
8using PlayTTS = aimdk_msgs::srv::PlayTts;
9
10int main(int argc, char **argv) {
11 rclcpp::init(argc, argv);
12 auto node = rclcpp::Node::make_shared("play_tts_client_min");
13
14 const std::string service_name = "/aimdk_5Fmsgs/srv/PlayTts";
15 auto client = node->create_client<PlayTTS>(service_name);
16
17 RCLCPP_INFO(node->get_logger(), "✅ PlayTts client node created.");
18
19 // Wait for the service to become available
20 while (!client->wait_for_service(std::chrono::seconds(2))) {
21 if (!rclcpp::ok()) {
22 rclcpp::shutdown();
23 return 1;
24 }
25 RCLCPP_INFO(node->get_logger(), "⏳ Service unavailable, waiting...");
26 }
27 RCLCPP_INFO(node->get_logger(),
28 "🟢 Service available, ready to send request.");
29
30 // Get text to speak
31 std::string tts_text;
32 if (argc > 1) {
33 tts_text = argv[1];
34 } else {
35 std::cout << "Enter text to speak: ";
36 if (!std::getline(std::cin, tts_text) || !rclcpp::ok()) {
37 std::cout << std::endl;
38 rclcpp::shutdown();
39 return 1;
40 }
41 if (tts_text.empty()) {
42 tts_text = "Hello, I am AgiBot X2.";
43 }
44 }
45
46 if (!rclcpp::ok()) {
47 rclcpp::shutdown();
48 return 1;
49 }
50
51 auto req = std::make_shared<PlayTTS::Request>();
52 req->tts_req.text = tts_text;
53 req->tts_req.domain = "demo_client"; // Required: identifies the caller
54 req->tts_req.trace_id =
55 "demo"; // Optional: request identifier for the TTS request
56 req->tts_req.is_interrupted =
57 true; // Required: whether to interrupt same-priority playback
58 req->tts_req.priority_weight = 0;
59 req->tts_req.priority_level.value =
60 aimdk_msgs::msg::TtsPriorityLevel::INTERACTION_L6; // L6 (6)
61
62 RCLCPP_INFO(node->get_logger(), "📨 Sending request to play tts: text=%s",
63 tts_text.c_str());
64
65 std::shared_future<PlayTTS::Response::SharedPtr> future;
66 rclcpp::FutureReturnCode rc = rclcpp::FutureReturnCode::TIMEOUT;
67 for (int i = 0; i < 8; ++i) {
68 req->header.header.stamp = node->now();
69 future = client->async_send_request(req);
70 rc = rclcpp::spin_until_future_complete(node, future,
71 std::chrono::milliseconds(250));
72 if (rc == rclcpp::FutureReturnCode::SUCCESS) {
73 break;
74 }
75 if (!rclcpp::ok()) {
76 rclcpp::shutdown();
77 return 1;
78 }
79 // retry as remote peer is NOT handled well by ROS
80 RCLCPP_INFO(node->get_logger(), "trying ... [%d]", i);
81 }
82
83 if (rc != rclcpp::FutureReturnCode::SUCCESS) {
84 if (rclcpp::ok()) {
85 RCLCPP_ERROR(node->get_logger(),
86 "❌ Service call not completed or timed out.");
87 }
88 rclcpp::shutdown();
89 return 1;
90 }
91
92 const auto resp = future.get();
93 if (resp->tts_resp.is_success) {
94 RCLCPP_INFO(node->get_logger(), "✅ TTS sent successfully.");
95 } else {
96 RCLCPP_ERROR(node->get_logger(),
97 "❌ Failed to send TTS: error_code=%u, error_message=%s",
98 resp->tts_resp.error_code,
99 resp->tts_resp.error_message.c_str());
100 }
101
102 rclcpp::shutdown();
103 return 0;
104}
使用说明
# 使用命令行参数播报文本(推荐)
ros2 run examples play_tts "你好,我是灵犀X2机器人"
# 或者不带参数运行,程序会提示用户输入
ros2 run examples play_tts
输出示例
[INFO] [1779181543.136645281] [play_tts_client_min]: ✅ PlayTts client node created.
[INFO] [1779181543.137467698] [play_tts_client_min]: 🟢 Service available, ready to send request.
[INFO] [1779181543.138450608] [play_tts_client_min]: 📨 Sending request to play tts: text=Hello
[INFO] [1779181543.146077262] [play_tts_client_min]: ✅ TTS sent successfully.
注意事项
确保TTS服务正常运行
支持中文和英文文本播报
优先级设置影响播放队列顺序
打断功能可中断当前播放的语音
接口参考
服务:
/aimdk_5Fmsgs/srv/PlayTts消息:
aimdk_msgs/srv/PlayTts
6.2.21 麦克风数据接收
该示例中用到了mic_receiver,通过订阅/agent/process_audio_output话题来接收机器人的降噪音频数据,支持内置麦克风和外置麦克风两种音频流,并根据VAD(语音活动检测)状态自动保存完整的语音片段为PCM文件。
功能特点:
基于VAD状态自动检测语音开始、处理中、结束
自动保存完整语音片段为PCM文件(单声道,采样率16kHz,格式S16LE)
按时间戳和音频流分类存储
支持音频时长计算和统计信息输出
应用场景:
语音识别和语音处理
音频数据采集和分析
实时语音监控
音频质量检测
多麦克风阵列数据处理
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 "Start subscribing to noise-suppressed 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 using V = aimdk_msgs::msg::AudioVadStateType;
51 static const std::unordered_map<uint8_t, std::string> vad_state_names = {
52 {V::AUDIO_VAD_STATE_BEGIN, "Speech start"},
53 {V::AUDIO_VAD_STATE_PROCESSING, "Speech in progress"},
54 {V::AUDIO_VAD_STATE_END, "Speech end"}};
55 static const std::unordered_map<uint32_t, std::string> stream_names = {
56 {1, "Built-in microphone"}, {2, "External microphone"}};
57
58 RCLCPP_INFO(this->get_logger(),
59 "Received audio data: 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 using V = aimdk_msgs::msg::AudioVadStateType;
83 static const std::unordered_map<uint8_t, std::string> vad_state_names = {
84 {V::AUDIO_VAD_STATE_BEGIN, "Speech start"},
85 {V::AUDIO_VAD_STATE_PROCESSING, "Speech in progress"},
86 {V::AUDIO_VAD_STATE_END, "Speech end"}};
87 static const std::unordered_map<uint32_t, std::string> stream_names = {
88 {1, "Built-in microphone"}, {2, "External microphone"}};
89
90 RCLCPP_INFO(this->get_logger(), "[%s] VAD state: %s audio: %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 if (vad_state == V::AUDIO_VAD_STATE_BEGIN) { // BEGIN (1)
100 RCLCPP_INFO(this->get_logger(), "🎤 Speech start detected");
101 if (recording_state_[stream_id] == false) {
102 audio_buffers_[stream_id].clear();
103 recording_state_[stream_id] = true;
104 }
105 if (!audio_data.empty()) {
106 audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
107 audio_data.begin(), audio_data.end());
108 }
109
110 } else if (vad_state == V::AUDIO_VAD_STATE_PROCESSING) { // PROCESSING (2)
111 RCLCPP_INFO(this->get_logger(), "🔄 Speech in progress...");
112 if (recording_state_[stream_id] && !audio_data.empty()) {
113 audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
114 audio_data.begin(), audio_data.end());
115 }
116
117 } else if (vad_state == V::AUDIO_VAD_STATE_END) { // END (3)
118 RCLCPP_INFO(this->get_logger(), "✅ Speech end");
119 if (recording_state_[stream_id] && !audio_data.empty()) {
120 audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
121 audio_data.begin(), audio_data.end());
122 }
123 if (recording_state_[stream_id] && !audio_buffers_[stream_id].empty()) {
124 save_audio_segment(audio_buffers_[stream_id], stream_id);
125 }
126 recording_state_[stream_id] = false;
127
128 // AUDIO_VAD_STATE_UNKNOWN
129 } else {
130 if (recording_state_[stream_id]) {
131 RCLCPP_INFO(this->get_logger(), "⏹️ Reset recording state");
132 recording_state_[stream_id] = false;
133 }
134 }
135
136 // Output the current buffer status.
137 size_t buffer_size = audio_buffers_[stream_id].size();
138 bool recording = recording_state_[stream_id];
139 RCLCPP_DEBUG(this->get_logger(),
140 "[Stream %u] Buffer size: %zu bytes, Recording state: %s",
141 stream_id, buffer_size, recording ? "true" : "false");
142 }
143
144 void save_audio_segment(const std::vector<uint8_t> &audio_data,
145 uint32_t stream_id) {
146 if (audio_data.empty())
147 return;
148
149 // Get the current timestamp.
150 auto now = std::chrono::system_clock::now();
151 std::time_t t = std::chrono::system_clock::to_time_t(now);
152 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
153 now.time_since_epoch()) %
154 1000;
155
156 std::ostringstream oss;
157 oss << std::put_time(std::localtime(&t), "%Y%m%d_%H%M%S") << "_"
158 << std::setw(3) << std::setfill('0') << ms.count();
159
160 // Create a subdirectory by stream_id.
161 fs::path stream_dir =
162 fs::path(audio_output_dir_) / ("stream_" + std::to_string(stream_id));
163 fs::create_directories(stream_dir);
164
165 static const std::unordered_map<uint32_t, std::string> stream_names = {
166 {1, "internal_mic"}, {2, "external_mic"}};
167 std::string stream_name = stream_names.count(stream_id)
168 ? stream_names.at(stream_id)
169 : ("stream_" + std::to_string(stream_id));
170 std::string filename = stream_name + "_" + oss.str() + ".pcm";
171 fs::path filepath = stream_dir / filename;
172
173 try {
174 std::ofstream ofs(filepath, std::ios::binary);
175 ofs.write(reinterpret_cast<const char *>(audio_data.data()),
176 audio_data.size());
177 ofs.close();
178 RCLCPP_INFO(this->get_logger(),
179 "Audio segment saved: %s (size: %zu bytes)", filepath.c_str(),
180 audio_data.size());
181
182 // Record audio file duration (assuming 16kHz, 16-bit, mono)
183 int sample_rate = 16000;
184 int bits_per_sample = 16;
185 int channels = 1;
186 int bytes_per_sample = bits_per_sample / 8;
187 size_t total_samples = audio_data.size() / (bytes_per_sample * channels);
188 double duration_seconds =
189 static_cast<double>(total_samples) / sample_rate;
190
191 RCLCPP_INFO(this->get_logger(), "Audio duration: %.2f s (%zu samples)",
192 duration_seconds, total_samples);
193 } catch (const std::exception &e) {
194 RCLCPP_ERROR(this->get_logger(), "Failed to save audio file: %s",
195 e.what());
196 }
197 }
198
199 // Member variables
200 std::unordered_map<uint32_t, std::vector<uint8_t>> audio_buffers_;
201 std::unordered_map<uint32_t, bool> recording_state_;
202 std::string audio_output_dir_;
203 rclcpp::Subscription<aimdk_msgs::msg::ProcessedAudioOutput>::SharedPtr
204 subscription_;
205};
206
207int main(int argc, char **argv) {
208 rclcpp::init(argc, argv);
209 auto node = std::make_shared<AudioSubscriber>();
210 RCLCPP_INFO(
211 node->get_logger(),
212 "Listening for noise-suppressed audio data, press Ctrl+C to exit...");
213 rclcpp::spin(node);
214 rclcpp::shutdown();
215 return 0;
216}
使用说明:
运行程序:
# 构建 colcon build --packages-select examples # 运行麦克风接收程序, 配合唤醒词触发VAD ros2 run examples mic_receiver
按 Ctrl+C 安全退出程序,程序会自动处理音频流的开始和结束。
查找录制的PCM文件:
运行节点后会自动创建
audio_recordings目录音频文件按stream_id分类存储(暂不区分, 一律使用stream_1):
stream_1/: 内置麦克风音频stream_2/: 外置麦克风音频
文件命名格式:
{stream_name}_{timestamp}.pcminternal_mic_20250909_133649_738.pcm(内置麦克风)external_mic_20250909_133650_123.pcm(外置麦克风)
播放保存的PCM文件:
# 播放内置麦克风录音 aplay -r 16000 -f S16_LE -c 1 audio_recordings/stream_1/internal_mic_20250909_133649_738.pcm
参数说明:
-r 16000 # 采样率16kHz
-f S16_LE # 16位小端格式
-c 1 # 单声道 你也可以用其他音频播放器(如Audacity)以原始PCM格式导入并播放。
转换为WAV格式(可选):
# 使用ffmpeg转换为WAV格式 ffmpeg -f s16le -ar 16000 -ac 1 -i external_mic_20250909_133649_738.pcm output.wav
输出示例:
正常启动和运行:
[INFO] [1779195658.978174026] [audio_subscriber]: Start subscribing to noise-suppressed audio data...
[INFO] [1779195658.978795319] [audio_subscriber]: Listening for noise-suppressed audio data, press Ctrl+C to exit...
检测到语音开始:
[INFO] [1779195676.331873180] [audio_subscriber]: Received audio data: stream_id=1, vad_state=1(Speech start), audio_size=2560 bytes
[INFO] [1779195676.332029112] [audio_subscriber]: [Built-in microphone] VAD state: Speech start audio: 2560 bytes
[INFO] [1779195676.332061655] [audio_subscriber]: 🎤 Speech start detected
语音处理过程:
[INFO] [1779195676.332590408] [audio_subscriber]: Received audio data: stream_id=1, vad_state=2(Speech in progress), audio_size=18560 bytes
[INFO] [1779195676.332690053] [audio_subscriber]: [Built-in microphone] VAD state: Speech in progress audio: 18560 bytes
[INFO] [1779195676.332720229] [audio_subscriber]: 🔄 Speech in progress...
...
语音结束和保存:
[INFO] [1779195676.881115234] [audio_subscriber]: Received audio data: stream_id=1, vad_state=3(Speech end), audio_size=2560 bytes
[INFO] [1779195676.881210656] [audio_subscriber]: [Built-in microphone] VAD state: Speech end audio: 2560 bytes
[INFO] [1779195676.881234399] [audio_subscriber]: ✅ Speech end
[INFO] [1779195676.882756437] [audio_subscriber]: Audio segment saved: audio_recordings/stream_1/internal_mic_20260519_210116_881.pcm (size: 42880 bytes)
[INFO] [1779195676.882860210] [audio_subscriber]: Audio duration: 1.34 s (21440 samples)
6.2.22 麦克风原始流式数据接收
该示例中用到了mic_raw_receiver,通过订阅/aima/hal/audio/capture话题来接收机器人的原始音频数据,识别麦克风/回采,自动按声道保存为PCM文件。
功能特点:
按时间戳和声道分类存储
自动保存声道音频数据为PCM文件(单声道,采样率16kHz,格式S16LE)
支持麦克风切换检测
1/**
2 * Microphone raw data receiving example
3 *
4 * This example subscribes to the `/aima/hal/audio/capture` topic to receive
5 * the robot's raw audio data. It supports both the built-in microphone and the
6 * external microphone audio streams, and automatically saves complete audio
7 * data as PCM files split by channel.
8 *
9 * Features:
10 * - Automatically saves raw audio as PCM files
11 * - Stores files categorized by timestamp and mic source
12 */
13
14#include <aimdk_msgs/msg/audio_capture.hpp>
15#include <chrono>
16#include <ctime>
17#include <filesystem>
18#include <fstream>
19#include <iomanip>
20#include <rclcpp/rclcpp.hpp>
21#include <sstream>
22#include <string>
23#include <unordered_map>
24#include <vector>
25
26namespace fs = std::filesystem;
27
28class RawAudioSubscriber : public rclcpp::Node {
29public:
30 RawAudioSubscriber() : rclcpp::Node("raw_audio_subscriber") {
31 mic_channels_ = 0;
32 ref_channels_ = 0;
33 inited_ = false;
34
35 // Create audio output directory named by current timestamp
36 auto now = std::chrono::system_clock::now();
37 auto time_t_now = std::chrono::system_clock::to_time_t(now);
38 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
39 now.time_since_epoch()) %
40 1000;
41 std::ostringstream oss;
42 oss << std::put_time(std::localtime(&time_t_now), "%Y%m%d_%H%M%S_")
43 << std::setw(3) << std::setfill('0') << ms.count();
44 audio_output_dir_ = "audio_recordings/" + oss.str();
45 fs::create_directories(audio_output_dir_);
46
47 // Note: deep queue to avoid missing data
48 auto qos = rclcpp::QoS(rclcpp::KeepLast(500)).best_effort();
49
50 subscription_ = this->create_subscription<aimdk_msgs::msg::AudioCapture>(
51 "/aima/hal/audio/capture", qos,
52 std::bind(&RawAudioSubscriber::audio_callback, this,
53 std::placeholders::_1));
54
55 RCLCPP_INFO(this->get_logger(), "Start subscribing to raw audio data...");
56 }
57
58 void run() {
59 timer_ = this->create_wall_timer(
60 std::chrono::seconds(1),
61 std::bind(&RawAudioSubscriber::save_audio_segments, this));
62 rclcpp::spin(shared_from_this());
63 }
64
65private:
66 void audio_callback(const aimdk_msgs::msg::AudioCapture::SharedPtr msg) {
67 if (!inited_) {
68 mic_channels_ = msg->mic_channels;
69 ref_channels_ = msg->ref_channels;
70 inited_ = true;
71 } else if (mic_channels_ != msg->mic_channels ||
72 ref_channels_ != msg->ref_channels) {
73 // quit as MIC switched
74 RCLCPP_ERROR(
75 this->get_logger(),
76 "MIC channels info changed (mic:%u ref:%u) -> (mic:%u ref:%u)",
77 mic_channels_, ref_channels_, msg->mic_channels, msg->ref_channels);
78 rclcpp::shutdown();
79 return;
80 }
81
82 handle_audio_data(msg->data.data);
83 }
84
85 void handle_audio_data(const std::vector<uint8_t> &audio_data) {
86 // Split S16LE data into channels
87 int channels =
88 static_cast<int>(ref_channels_) + static_cast<int>(mic_channels_);
89 if (channels == 0)
90 return;
91
92 const int bytes_per_channel = 2;
93 const int unit_size = channels * bytes_per_channel;
94 size_t num_frames = audio_data.size() / unit_size;
95
96 for (size_t k = 0; k < num_frames; ++k) {
97 for (int i = 0; i < channels; ++i) {
98 size_t base = k * unit_size + i * bytes_per_channel;
99 audio_buffers_[i].push_back(audio_data[base]);
100 audio_buffers_[i].push_back(audio_data[base + 1]);
101 }
102 }
103 }
104
105 void save_audio_segments() {
106 RCLCPP_INFO(this->get_logger(), "Flushing cached audio data...");
107 int total =
108 static_cast<int>(mic_channels_) + static_cast<int>(ref_channels_);
109 for (int i = 0; i < total; ++i) {
110 bool is_ref = (i >= static_cast<int>(mic_channels_));
111 save_audio_segment(i, is_ref);
112 }
113 }
114
115 void save_audio_segment(int channel, bool is_ref) {
116 auto &buf = audio_buffers_[channel];
117 if (buf.empty())
118 return;
119
120 std::string channel_type = is_ref ? "ref" : "mic";
121 std::string filename =
122 "channel_" + std::to_string(channel) + "_" + channel_type + ".pcm";
123 fs::path filepath = fs::path(audio_output_dir_) / filename;
124
125 try {
126 // Append mode (equivalent to Python 'ab')
127 std::ofstream ofs(filepath, std::ios::binary | std::ios::app);
128 ofs.write(reinterpret_cast<const char *>(buf.data()), buf.size());
129 ofs.close();
130
131 RCLCPP_INFO(this->get_logger(),
132 "Audio segment saved: %s appended: %zu bytes",
133 filepath.c_str(), buf.size());
134 buf.clear();
135 } catch (const std::exception &e) {
136 RCLCPP_ERROR(this->get_logger(), "Failed to save audio file: %s",
137 e.what());
138 }
139 }
140
141 bool inited_;
142 uint8_t mic_channels_;
143 uint8_t ref_channels_;
144 std::string audio_output_dir_;
145 std::unordered_map<int, std::vector<uint8_t>> audio_buffers_;
146 rclcpp::Subscription<aimdk_msgs::msg::AudioCapture>::SharedPtr subscription_;
147 rclcpp::TimerBase::SharedPtr timer_;
148};
149
150int main(int argc, char **argv) {
151 rclcpp::init(argc, argv);
152 auto node = std::make_shared<RawAudioSubscriber>();
153 RCLCPP_INFO(node->get_logger(),
154 "Listening to raw audio data, press Ctrl+C to exit...");
155 node->run();
156 rclcpp::shutdown();
157 return 0;
158}
使用说明:
运行程序:
# 构建 colcon build --packages-select examples # 运行麦克风接收程序 ros2 run examples mic_raw_receiver
查找录制的PCM文件:
运行节点后会自动创建
audio_recordings目录音频文件按录制程序本次启动时间归集存储至如下格式的目录:
20250909_133650_123
文件命名格式:
channel_{index}_{type}.pcm,如channel_0_mic.pcm(麦克风)channel_4_ref.pcm(回采)
播放保存的PCM文件:
转到计算机上播放:
aplay -r 16000 -f S16_LE -c 1 audio_recordings/20250909_133650_123/channel_0_mic.pcm
机器人内播放:
# 使用原始流式音频播放例程 ros2 run examples play_audio_stream --ros-args -p raw_audio_path:=$PWD/audio_recordings/20250909_133650_123/channel_0_mic.pcm # 或使用媒体文件播放例程, 注意提前将文件转存到交互板内, 并使用其路径替换下方pcm路径 ros2 run examples play_audio please/use/path/on/PC3.pcm
6.2.23 获取当前麦克风
1#include "aimdk_msgs/msg/common_request.hpp"
2#include "aimdk_msgs/msg/common_state.hpp"
3#include "aimdk_msgs/srv/get_mic_source_request.hpp"
4#include "rclcpp/rclcpp.hpp"
5#include <chrono>
6#include <memory>
7#include <signal.h>
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 GetMicSourceRequestClient : public rclcpp::Node {
22public:
23 GetMicSourceRequestClient() : Node("get_mic_source_request_client") {
24 client_ = this->create_client<aimdk_msgs::srv::GetMicSourceRequest>(
25 "/aimdk_5Fmsgs/srv/GetMicSourceRequest");
26
27 RCLCPP_INFO(this->get_logger(),
28 "✅ GetMicSourceRequest client node created.");
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 void send_request() {
39 try {
40 auto request =
41 std::make_shared<aimdk_msgs::srv::GetMicSourceRequest::Request>();
42 request->header = aimdk_msgs::msg::CommonRequest();
43
44 RCLCPP_INFO(this->get_logger(), "📨 Sending request to get MIC source");
45
46 auto timeout = std::chrono::milliseconds(250);
47 for (int i = 0; i < 8; i++) {
48 request->header.header.stamp = this->now();
49 auto future = client_->async_send_request(request);
50 auto retcode = rclcpp::spin_until_future_complete(
51 this->shared_from_this(), 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 if (response->header.status.value ==
60 aimdk_msgs::msg::CommonState::SUCCESS) { // SUCCESS (1)
61 RCLCPP_INFO(this->get_logger(), "✅ MIC source get successfully.");
62 RCLCPP_INFO(this->get_logger(), "MIC id: %d", response->mic_source);
63 } else {
64 RCLCPP_ERROR(this->get_logger(), "❌ Failed to get MIC source: %s",
65 response->header.message.c_str());
66 }
67 return;
68 }
69 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
70 } catch (const std::exception &e) {
71 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
72 }
73 }
74
75private:
76 rclcpp::Client<aimdk_msgs::srv::GetMicSourceRequest>::SharedPtr client_;
77};
78
79int main(int argc, char *argv[]) {
80 try {
81 rclcpp::init(argc, argv);
82 signal(SIGINT, signal_handler);
83 signal(SIGTERM, signal_handler);
84
85 g_node = std::make_shared<GetMicSourceRequestClient>();
86 auto client = std::dynamic_pointer_cast<GetMicSourceRequestClient>(g_node);
87
88 if (client) {
89 client->send_request();
90 }
91
92 g_node.reset();
93 rclcpp::shutdown();
94 return 0;
95 } catch (const std::exception &e) {
96 RCLCPP_ERROR(rclcpp::get_logger("main"),
97 "Program exited with exception: %s", e.what());
98 return 1;
99 }
100}
6.2.24 切换麦克风
1#include "aimdk_msgs/msg/common_request.hpp"
2#include "aimdk_msgs/msg/common_state.hpp"
3#include "aimdk_msgs/srv/set_mic_source_request.hpp"
4#include "rclcpp/rclcpp.hpp"
5#include <chrono>
6#include <map>
7#include <memory>
8#include <signal.h>
9#include <string>
10
11std::shared_ptr<rclcpp::Node> g_node = nullptr;
12
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 SetMicSourceRequestClient : public rclcpp::Node {
24public:
25 SetMicSourceRequestClient() : Node("set_mic_source_request_client") {
26 client_ = this->create_client<aimdk_msgs::srv::SetMicSourceRequest>(
27 "/aimdk_5Fmsgs/srv/SetMicSourceRequest");
28
29 RCLCPP_INFO(this->get_logger(),
30 "✅ SetMicSourceRequest 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 void send_request(int mic_source) {
41 try {
42 auto request =
43 std::make_shared<aimdk_msgs::srv::SetMicSourceRequest::Request>();
44 request->header = aimdk_msgs::msg::CommonRequest();
45 request->mic_source = mic_source;
46
47 RCLCPP_INFO(this->get_logger(), "📨 Sending request to set mic source: %d",
48 mic_source);
49
50 auto timeout = std::chrono::milliseconds(250);
51 for (int i = 0; i < 8; i++) {
52 request->header.header.stamp = this->now();
53 auto future = client_->async_send_request(request);
54 auto retcode = rclcpp::spin_until_future_complete(
55 this->shared_from_this(), future, timeout);
56 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
57 // retry as remote peer is NOT handled well by ROS
58 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
59 continue;
60 }
61 // future.done
62 auto response = future.get();
63 if (response->header.status.value ==
64 aimdk_msgs::msg::CommonState::SUCCESS) { // SUCCESS (1)
65 RCLCPP_INFO(this->get_logger(), "✅ MIC source set successfully.");
66 } else {
67 RCLCPP_ERROR(this->get_logger(), "❌ Failed to set MIC source: %s",
68 response->header.message.c_str());
69 }
70 return;
71 }
72 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
73 } catch (const std::exception &e) {
74 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
75 }
76 }
77
78private:
79 rclcpp::Client<aimdk_msgs::srv::SetMicSourceRequest>::SharedPtr client_;
80};
81
82int main(int argc, char *argv[]) {
83 // abbr -> mic_id mapping
84 // 0: internal MIC, 1: external MIC
85 const std::map<std::string, int> choices = {{"int", 0}, {"ext", 1}};
86
87 try {
88 rclcpp::init(argc, argv);
89 signal(SIGINT, signal_handler);
90 signal(SIGTERM, signal_handler);
91
92 std::string mic_abbr;
93 if (argc > 1) {
94 mic_abbr = argv[1];
95 } else {
96 printf("%-4s - %-6s : %s\n", "abbr", "mic_id", "description");
97 printf("%-4s - %-6d : %s\n", "int", 0, "internal MIC");
98 printf("%-4s - %-6d : %s\n", "ext", 1, "external MIC");
99 printf("Enter abbr of MIC source: ");
100 std::cin >> mic_abbr;
101 }
102
103 auto it = choices.find(mic_abbr);
104 if (it == choices.end()) {
105 RCLCPP_ERROR(rclcpp::get_logger("main"), "Invalid abbr of MIC source: %s",
106 mic_abbr.c_str());
107 return 1;
108 }
109 int mic_id = it->second;
110
111 g_node = std::make_shared<SetMicSourceRequestClient>();
112 auto client = std::dynamic_pointer_cast<SetMicSourceRequestClient>(g_node);
113
114 if (client) {
115 client->send_request(mic_id);
116 }
117
118 g_node.reset();
119 rclcpp::shutdown();
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}
使用说明
# 构建
colcon build --packages-select examples
# 交互式运行
ros2 run examples set_mic_source
# 命令行参数运行
ros2 run examples set_mic_source ext # ext - 外置麦, int - 内置麦
6.2.25 原始流式音频播放
该示例中用到了play_audio_stream,先申请音频焦点,再将PCM文件以实时速率发布到/aima/hal/audio/playback话题进行播放;焦点被抢时自动退出。
1/**
2 * Raw audio stream playback example
3 *
4 * This example gets audio focus first and then publishes raw audio data at a
5 * fixed rate to the `/aima/hal/audio/playback` topic. It stops playing when
6 * audio focus is lost.
7 *
8 * Usage:
9 * ros2 run examples play_audio_stream --ros-args -p
10 * raw_audio_path:=/path/to/file.pcm
11 */
12
13#include <aimdk_msgs/msg/audio_data.hpp>
14#include <aimdk_msgs/msg/audio_info.hpp>
15#include <aimdk_msgs/msg/audio_playback.hpp>
16#include <aimdk_msgs/msg/common_state.hpp>
17#include <aimdk_msgs/msg/focus_requester.hpp>
18#include <aimdk_msgs/msg/focus_response.hpp>
19#include <aimdk_msgs/srv/abandon_audio_focus.hpp>
20#include <aimdk_msgs/srv/request_audio_focus.hpp>
21#include <chrono>
22#include <csignal>
23#include <fstream>
24#include <rclcpp/rclcpp.hpp>
25#include <string>
26#include <thread>
27#include <vector>
28
29// ---------------------------------------------------------------------------
30// FakeMicDevice: simulates a real-time microphone from a raw PCM file
31// ---------------------------------------------------------------------------
32class FakeMicDevice {
33public:
34 explicit FakeMicDevice(const std::string &raw_audio_file)
35 : channels_(1), sample_rate_(16000), bytes_per_sample_(2), index_(0),
36 start_time_(std::chrono::steady_clock::time_point{}) {
37 std::ifstream f(raw_audio_file, std::ios::binary);
38 if (!f.is_open()) {
39 throw std::runtime_error("Cannot open audio file: " + raw_audio_file);
40 }
41 audio_data_ = std::vector<uint8_t>(std::istreambuf_iterator<char>(f),
42 std::istreambuf_iterator<char>());
43 }
44
45 int get_channel_count() const { return channels_; }
46 int get_sample_rate() const { return sample_rate_; }
47
48 std::vector<uint8_t> get_cached_data() {
49 auto now = std::chrono::steady_clock::now();
50 if (start_time_ == std::chrono::steady_clock::time_point{}) {
51 // fake init: assume audio data started 1s ago
52 start_time_ = now - std::chrono::seconds(1);
53 }
54
55 double elapsed = std::chrono::duration<double>(now - start_time_).count();
56 size_t tail = static_cast<size_t>(elapsed * sample_rate_) * channels_ *
57 bytes_per_sample_;
58
59 std::vector<uint8_t> data;
60 if (tail < audio_data_.size()) {
61 data = std::vector<uint8_t>(audio_data_.begin() + index_,
62 audio_data_.begin() + tail);
63 index_ = tail;
64 } else {
65 // loopback
66 data =
67 std::vector<uint8_t>(audio_data_.begin() + index_, audio_data_.end());
68 index_ = 0;
69 start_time_ = now;
70 }
71 return data;
72 }
73
74private:
75 int channels_;
76 int sample_rate_;
77 int bytes_per_sample_;
78 size_t index_;
79 std::chrono::steady_clock::time_point start_time_;
80 std::vector<uint8_t> audio_data_;
81};
82
83// ---------------------------------------------------------------------------
84// AudioStreamPlayer node
85// ---------------------------------------------------------------------------
86using RequestAudioFocus = aimdk_msgs::srv::RequestAudioFocus;
87using AbandonAudioFocus = aimdk_msgs::srv::AbandonAudioFocus;
88
89class AudioStreamPlayer : public rclcpp::Node {
90public:
91 AudioStreamPlayer() : rclcpp::Node("audio_stream_player") {
92 this->declare_parameter<std::string>("raw_audio_path", "");
93 std::string raw_audio_path =
94 this->get_parameter("raw_audio_path").as_string();
95
96 mic_device_ = std::make_unique<FakeMicDevice>(raw_audio_path);
97
98 pkg_name_ = "audio_stream_player" + std::to_string(getpid());
99 focus_ = false;
100 focus_force_ = true;
101
102 RCLCPP_INFO(this->get_logger(), "local pkg name: %s", pkg_name_.c_str());
103
104 // Create focus request/release clients
105 request_client_ = this->create_client<RequestAudioFocus>(
106 "/aimdk_5Fmsgs/srv/RequestAudioFocus");
107 release_client_ = this->create_client<AbandonAudioFocus>(
108 "/aimdk_5Fmsgs/srv/AbandonAudioFocus");
109
110 // Wait for service to become available
111 while (!request_client_->wait_for_service(std::chrono::seconds(2))) {
112 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
113 }
114 RCLCPP_INFO(this->get_logger(),
115 "🟢 Service available, ready to send request.");
116
117 // Subscriber: focus events
118 auto focus_qos = rclcpp::QoS(rclcpp::KeepLast(10)).reliable();
119 sub_ = this->create_subscription<aimdk_msgs::msg::FocusResponse>(
120 "/aima/hal/audio/focus_response", focus_qos,
121 std::bind(&AudioStreamPlayer::focus_event_callback, this,
122 std::placeholders::_1));
123
124 // Publisher: raw audio playback
125 pub_ = this->create_publisher<aimdk_msgs::msg::AudioPlayback>(
126 "/aima/hal/audio/playback", 10);
127 }
128
129 void send_request(bool focus) {
130 if (focus) {
131 send_focus_request();
132 } else {
133 send_focus_release();
134 }
135 }
136
137 bool is_holding_focus() const { return focus_ && focus_force_; }
138
139 void spin_until_focus() {
140 while (rclcpp::ok()) {
141 focus_force_ = true;
142 send_request(true);
143 if (is_holding_focus())
144 return;
145
146 RCLCPP_INFO(this->get_logger(), "need retry to get focus");
147 auto t0 = std::chrono::steady_clock::now();
148 while (rclcpp::ok()) {
149 auto t1 = std::chrono::steady_clock::now();
150 if (std::chrono::duration<double>(t1 - t0).count() >= 1.0)
151 break;
152 rclcpp::spin_some(shared_from_this());
153 std::this_thread::sleep_for(std::chrono::milliseconds(100));
154 }
155 }
156 }
157
158 void run() {
159 RCLCPP_INFO(this->get_logger(), "🟢 publishing audio data ...");
160 while (rclcpp::ok() && is_holding_focus()) {
161 run_once();
162 rclcpp::spin_some(shared_from_this());
163 std::this_thread::sleep_for(std::chrono::milliseconds(50));
164 }
165 if (!is_holding_focus()) {
166 RCLCPP_INFO(this->get_logger(), "focus out, exiting...");
167 }
168 }
169
170private:
171 void send_focus_request() {
172 auto req = std::make_shared<RequestAudioFocus::Request>();
173 aimdk_msgs::msg::FocusRequester requester;
174 requester.pkg_name = pkg_name_;
175 requester.priority = kAudioPriority;
176 req->focus_requester = requester;
177
178 RCLCPP_INFO(this->get_logger(), "📨 Sending RequestAudioFocus request");
179 std::shared_future<RequestAudioFocus::Response::SharedPtr> future;
180 for (int i = 0; i < 8; ++i) {
181 future = request_client_->async_send_request(req).future.share();
182 auto status = rclcpp::spin_until_future_complete(
183 shared_from_this(), future, std::chrono::milliseconds(250));
184 if (status == rclcpp::FutureReturnCode::SUCCESS)
185 break;
186 // retry as remote peer is NOT handled well by ROS
187 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
188 }
189
190 auto response = future.get();
191 if (!response) {
192 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
193 return;
194 }
195 if (response->reponse.status.value ==
196 aimdk_msgs::msg::CommonState::SUCCESS) { // SUCCESS (1)
197 focus_ = response->focus_response.focus_gain;
198 RCLCPP_INFO(this->get_logger(), "✅ RequestAudioFocus done: focus %s",
199 focus_ ? "true" : "false");
200 } else {
201 RCLCPP_ERROR(this->get_logger(),
202 "❌ Failed in response of RequestAudioFocus: %s",
203 response->reponse.message.c_str());
204 }
205 }
206
207 void send_focus_release() {
208 auto req = std::make_shared<AbandonAudioFocus::Request>();
209 aimdk_msgs::msg::FocusRequester requester;
210 requester.pkg_name = pkg_name_;
211 requester.priority = kAudioPriority; // must match RequestAudioFocus
212 req->focus_requester = requester;
213
214 RCLCPP_INFO(this->get_logger(), "📨 Sending AbandonAudioFocus request");
215 std::shared_future<AbandonAudioFocus::Response::SharedPtr> future;
216 for (int i = 0; i < 8; ++i) {
217 future = release_client_->async_send_request(req).future.share();
218 auto status = rclcpp::spin_until_future_complete(
219 shared_from_this(), future, std::chrono::milliseconds(250));
220 if (status == rclcpp::FutureReturnCode::SUCCESS)
221 break;
222 // retry as remote peer is NOT handled well by ROS
223 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
224 }
225
226 auto response = future.get();
227 if (!response) {
228 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
229 return;
230 }
231 if (response->reponse.status.value ==
232 aimdk_msgs::msg::CommonState::SUCCESS) { // SUCCESS (1)
233 // always focus False after abandon
234 focus_ = response->focus_response.focus_gain;
235 RCLCPP_INFO(this->get_logger(), "✅ AbandonAudioFocus done: focus %s",
236 focus_ ? "true" : "false");
237 } else {
238 RCLCPP_ERROR(this->get_logger(),
239 "❌ Failed in response of AbandonAudioFocus: %s",
240 response->reponse.message.c_str());
241 }
242 }
243
244 void
245 focus_event_callback(const aimdk_msgs::msg::FocusResponse::SharedPtr msg) {
246 if (msg->pkg_name == pkg_name_) {
247 RCLCPP_INFO(this->get_logger(),
248 "receive focus out event: focus state: %s",
249 msg->focus_gain ? "true" : "false");
250 focus_force_ = msg->focus_gain;
251 }
252 }
253
254 void run_once() {
255 aimdk_msgs::msg::AudioPlayback msg;
256 msg.pkg_name = pkg_name_;
257 msg.token_id = pkg_name_;
258
259 aimdk_msgs::msg::AudioInfo info;
260 info.channels = static_cast<uint8_t>(mic_device_->get_channel_count());
261 info.sample_rate = static_cast<uint32_t>(mic_device_->get_sample_rate());
262
263 aimdk_msgs::msg::AudioData audio_data;
264 audio_data.data = mic_device_->get_cached_data();
265
266 msg.info = info;
267 msg.data = audio_data;
268 pub_->publish(msg);
269 }
270
271 std::string pkg_name_;
272 bool focus_;
273 bool focus_force_;
274 static constexpr uint32_t kAudioPriority = 8;
275 std::unique_ptr<FakeMicDevice> mic_device_;
276 rclcpp::Client<RequestAudioFocus>::SharedPtr request_client_;
277 rclcpp::Client<AbandonAudioFocus>::SharedPtr release_client_;
278 rclcpp::Subscription<aimdk_msgs::msg::FocusResponse>::SharedPtr sub_;
279 rclcpp::Publisher<aimdk_msgs::msg::AudioPlayback>::SharedPtr pub_;
280};
281
282// ---------------------------------------------------------------------------
283// Signal handling
284// ---------------------------------------------------------------------------
285static std::shared_ptr<AudioStreamPlayer> g_node;
286
287void signal_handler(int sig) {
288 if (g_node) {
289 if (g_node->is_holding_focus()) {
290 g_node->send_request(false);
291 }
292 RCLCPP_INFO(g_node->get_logger(),
293 "Received signal %d, abandon audio focus and shutting down",
294 sig);
295 }
296 rclcpp::shutdown();
297}
298
299int main(int argc, char **argv) {
300 rclcpp::init(argc, argv);
301
302 try {
303 auto node = std::make_shared<AudioStreamPlayer>();
304 g_node = node;
305 std::signal(SIGINT, signal_handler);
306 std::signal(SIGTERM, signal_handler);
307
308 node->spin_until_focus();
309 node->run();
310
311 } catch (const std::exception &e) {
312 RCLCPP_ERROR(rclcpp::get_logger("main"),
313 "Program exited with exception: %s", e.what());
314 }
315
316 if (rclcpp::ok()) {
317 rclcpp::shutdown();
318 }
319 return 0;
320}
使用说明:
# 注意替换/path/to/your/raw_audio_file.pcm为原始音频文件路径,单通道,采样率16kHz,格式S16LE
ros2 run examples play_audio_stream --ros-args -p raw_audio_path:=/path/to/your/raw_audio_file.pcm
6.2.26 表情控制
该示例中用到了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 <iostream>
6#include <memory>
7#include <signal.h>
8#include <string>
9
10// Global variable used for signal handling
11std::shared_ptr<rclcpp::Node> g_node = nullptr;
12
13// Signal handler function
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 PlayEmojiClient : public rclcpp::Node {
25public:
26 PlayEmojiClient() : Node("play_emoji_client") {
27 client_ = this->create_client<aimdk_msgs::srv::PlayEmoji>(
28 "/aimdk_5Fmsgs/srv/PlayEmoji");
29 RCLCPP_INFO(this->get_logger(), "✅ PlayEmoji client node started.");
30
31 // Wait for the service to become available
32 while (!client_->wait_for_service(std::chrono::seconds(2))) {
33 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
34 }
35 RCLCPP_INFO(this->get_logger(),
36 "🟢 Service available, ready to send request.");
37 }
38
39 bool send_request(uint8_t emoji, uint8_t mode, int32_t priority) {
40 try {
41 auto request = std::make_shared<aimdk_msgs::srv::PlayEmoji::Request>();
42
43 request->emotion_id = emoji;
44 request->mode = mode;
45 request->priority = priority;
46
47 RCLCPP_INFO(
48 this->get_logger(),
49 "📨 Sending request to play emoji: id=%hhu, mode=%hhu, priority=%d",
50 emoji, mode, priority);
51
52 const std::chrono::milliseconds timeout(250);
53 for (int i = 0; i < 8; i++) {
54 request->header.header.stamp = this->now();
55 auto future = client_->async_send_request(request);
56 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
57 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->success) {
66 RCLCPP_INFO(this->get_logger(),
67 "✅ Request to play emoji recorded successfully: %s",
68 response->message.c_str());
69 return true;
70 } else {
71 RCLCPP_ERROR(this->get_logger(),
72 "❌ Failed to record play-emoji request: %s",
73 response->message.c_str());
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::PlayEmoji>::SharedPtr client_;
87};
88
89int main(int argc, char **argv) {
90 try {
91 rclcpp::init(argc, argv);
92
93 // Set up signal handlers
94 signal(SIGINT, signal_handler);
95 signal(SIGTERM, signal_handler);
96
97 int32_t priority = 10;
98
99 int emotion = 1; // Expression type, 1 means Blink
100 std::cout
101 << "Enter expression ID: 1-Blink, 60-Bored, 70-Abnormal, 80-Sleeping, "
102 "90-Happy, 190-Very Angry, 200-Adoration"
103 << std::endl;
104 std::cin >> emotion;
105
106 int mode = 1; // Playback mode, 1 means play once, 2 means loop
107 std::cout << "Enter play mode (1: once, 2: loop): ";
108 std::cin >> mode;
109 if (mode < 1 || mode > 2) {
110 RCLCPP_ERROR(rclcpp::get_logger("main"), "Invalid play mode: %d", mode);
111 rclcpp::shutdown();
112 return 1;
113 }
114
115 g_node = std::make_shared<PlayEmojiClient>();
116 auto client = std::dynamic_pointer_cast<PlayEmojiClient>(g_node);
117
118 if (client) {
119 client->send_request(emotion, mode, priority);
120 }
121
122 // Clean up resources
123 g_node.reset();
124 rclcpp::shutdown();
125
126 return 0;
127 } catch (const std::exception &e) {
128 RCLCPP_ERROR(rclcpp::get_logger("main"),
129 "Program exited with exception: %s", e.what());
130 return 1;
131 }
132}
6.2.27 地图构建
该示例中用到了slam,通过该节点给建图服务发送消息,实现建图功能
1#include <iostream>
2#include <rclcpp/rclcpp.hpp>
3#include <std_msgs/msg/string.hpp>
4#include <string>
5
6// Class definition for publishing commands to control SLAM operations
7class SlamCommandPublisher : public rclcpp::Node {
8public:
9 // Constructor for the SlamCommandPublisher class
10 SlamCommandPublisher()
11 : Node("slam_command_publisher") // Initialize the node with the name
12 // "slam_command_publisher"
13 {
14 // Match SLAM subscriber QoS: TRANSIENT_LOCAL + RELIABLE
15 rclcpp::QoS qos(10);
16 qos.transient_local().reliable();
17 publisher_ = this->create_publisher<std_msgs::msg::String>(
18 "/integrated_command", qos);
19 }
20
21 // Method to publish the "start_mapping" command
22 void publish_start_mapping() {
23 auto message = std_msgs::msg::String();
24 message.data = "start_mapping";
25 RCLCPP_INFO(this->get_logger(), "Publishing: [%s]", message.data.c_str());
26 publisher_->publish(message);
27 }
28
29 // Method to publish the "stop_mapping" command with a specified map name
30 void publish_stop_mapping(const std::string &map_name) {
31 auto message = std_msgs::msg::String();
32 message.data = "stop_mapping:" + map_name;
33 RCLCPP_INFO(this->get_logger(), "Publishing: [%s]", message.data.c_str());
34 publisher_->publish(message);
35 }
36
37private:
38 // Publisher object to publish messages to the "/integrated_command" topic
39 rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
40};
41
42int main(int argc, char *argv[]) {
43 // Initialize the ROS 2 communication system
44 rclcpp::init(argc, argv);
45 // Create an instance of the SlamCommandPublisher node
46 auto node = std::make_shared<SlamCommandPublisher>();
47
48 int input;
49 std::string map_name;
50
51 // Infinite loop to continuously accept user input
52 while (rclcpp::ok()) {
53 std::cout << "Enter 1 to start mapping, 2 to stop mapping: ";
54 std::cin >> input;
55
56 if (input == 1) {
57 // Publish the "start_mapping" command
58 node->publish_start_mapping();
59 } else if (input == 2) {
60 // Prompt the user to enter the map name
61 std::cout << "Enter map name: ";
62 std::cin >> map_name;
63 // Publish the "stop_mapping" command with the specified map name
64 node->publish_stop_mapping(map_name);
65 break;
66 } else {
67 // Handle invalid input
68 std::cout << "Invalid input. Please enter 1 or 2." << std::endl;
69 }
70 }
71
72 // Shutdown the ROS 2 communication system
73 rclcpp::shutdown();
74 return 0;
75}
使用说明:
ros2 run examples slam
Enter 1 to start mapping, 2 to stop mapping: 1
[INFO] [1772528788.353934160] [slam_command_publisher]: Publishing: [start_mapping]
Enter 1 to start mapping, 2 to stop mapping: 2
Enter map name: testmap
[INFO] [1772528791.900924187] [slam_command_publisher]: Publishing: [stop_mapping:testmap]
开始建图之后,推动机器,或者控制机器人移动,完成建图
6.2.28 使用地图进行重定位
该示例中用到地图构建步骤完成构建的地图进行定位
1#include <chrono>
2#include <functional>
3#include <geometry_msgs/msg/pose.hpp>
4#include <iostream>
5#include <nav_msgs/msg/odometry.hpp>
6#include <rclcpp/rclcpp.hpp>
7#include <std_msgs/msg/string.hpp>
8#include <string>
9
10using namespace std::chrono_literals;
11
12class RelocalizationNode : public rclcpp::Node {
13public:
14 RelocalizationNode(uint64_t map_id, double init_x, double init_y)
15 : Node("relocalization_node"), map_id_(map_id), init_x_(init_x),
16 init_y_(init_y) {
17 // /integrated_command subscriber requires TRANSIENT_LOCAL durability
18 auto command_qos = rclcpp::QoS(rclcpp::KeepLast(10));
19 command_qos.transient_local();
20
21 integrated_command_pub_ = this->create_publisher<std_msgs::msg::String>(
22 "/integrated_command", command_qos);
23 relocalization_pose_pub_ = this->create_publisher<geometry_msgs::msg::Pose>(
24 "/relocalization_pose", 10);
25
26 // Create subscriber with BEST_EFFORT QoS
27 auto lidar_loc_qos = rclcpp::QoS(rclcpp::KeepLast(10));
28 lidar_loc_qos.best_effort();
29
30 odometry_sub_ = this->create_subscription<nav_msgs::msg::Odometry>(
31 "/slam/lidar_odom", lidar_loc_qos,
32 std::bind(&RelocalizationNode::odometryCallback, this,
33 std::placeholders::_1));
34
35 // Wait for subscribers before publishing
36 publish_timer_ = this->create_wall_timer(200ms, [this]() {
37 // Wait until both publishers have at least one subscriber
38 if (integrated_command_pub_->get_subscription_count() == 0 ||
39 relocalization_pose_pub_->get_subscription_count() == 0) {
40 RCLCPP_INFO_THROTTLE(
41 this->get_logger(), *this->get_clock(), 2000,
42 "Waiting for subscribers... (command: %zu, pose: %zu)",
43 integrated_command_pub_->get_subscription_count(),
44 relocalization_pose_pub_->get_subscription_count());
45 return;
46 }
47 publish_timer_->cancel();
48
49 // Publish integrated_command
50 auto integrated_command_msg = std_msgs::msg::String();
51 integrated_command_msg.data =
52 "start_relocalization:" + std::to_string(map_id_);
53 integrated_command_pub_->publish(integrated_command_msg);
54 RCLCPP_INFO(this->get_logger(), "Published integrated_command");
55
56 // Schedule relocalization_pose publication after delay
57 this->relocalization_pose_timer_ = this->create_wall_timer(1s, [this]() {
58 this->relocalization_pose_timer_->cancel();
59
60 auto relocalization_pose_msg = geometry_msgs::msg::Pose();
61 relocalization_pose_msg.position.x = init_x_;
62 relocalization_pose_msg.position.y = init_y_;
63 relocalization_pose_msg.position.z = 0.0;
64 relocalization_pose_msg.orientation.x = 0.0;
65 relocalization_pose_msg.orientation.y = 0.0;
66 relocalization_pose_msg.orientation.z = 0.0;
67 relocalization_pose_msg.orientation.w = 1.0;
68
69 relocalization_pose_pub_->publish(relocalization_pose_msg);
70 RCLCPP_INFO(this->get_logger(), "Published relocalization_pose");
71
72 // Start timeout timer (30 seconds)
73 this->timeout_timer_ = this->create_wall_timer(30s, [this]() {
74 if (!success_received_) {
75 RCLCPP_ERROR(this->get_logger(),
76 "Timeout reached - Relocalization failed!");
77 rclcpp::shutdown();
78 }
79 });
80
81 RCLCPP_INFO(this->get_logger(),
82 "Waiting for robot pose data (timeout: 30s)...");
83 });
84 });
85 }
86
87private:
88 // get robot pose here
89 void odometryCallback(const nav_msgs::msg::Odometry::SharedPtr msg) {
90 if (!success_received_) {
91 success_received_ = true;
92 if (timeout_timer_) {
93 timeout_timer_->cancel();
94 }
95 RCLCPP_INFO(this->get_logger(),
96 "Received odometry data - Relocalization successful!");
97 rclcpp::shutdown();
98 }
99 }
100
101 rclcpp::Publisher<std_msgs::msg::String>::SharedPtr integrated_command_pub_;
102 rclcpp::Publisher<geometry_msgs::msg::Pose>::SharedPtr
103 relocalization_pose_pub_;
104 rclcpp::Subscription<nav_msgs::msg::Odometry>::SharedPtr odometry_sub_;
105
106 // Timers
107 rclcpp::TimerBase::SharedPtr publish_timer_;
108 rclcpp::TimerBase::SharedPtr relocalization_pose_timer_;
109 rclcpp::TimerBase::SharedPtr timeout_timer_;
110
111 uint64_t map_id_;
112 double init_x_;
113 double init_y_;
114 bool success_received_ = false;
115};
116
117int main(int argc, char *argv[]) {
118 rclcpp::init(argc, argv);
119
120 // Inputs needed for relocalization. To find them, see:
121 // map_id -> sqlite3 /agibot/data/var/MapManagerModule/map.db
122 // "SELECT * FROM map;"
123 // init_x/init_y -> origin (u, v) in
124 // /agibot/data/var/MapManagerModule/<map_id>/grid_map_info.txt
125 // (line 2; pixel coordinates)
126 uint64_t map_id = 0;
127 double init_x = 0.0;
128 double init_y = 0.0;
129 try {
130 std::string line;
131 std::cout << "Enter map_id: ";
132 std::getline(std::cin, line);
133 map_id = std::stoull(line);
134 std::cout << "Enter initial position.x (pixel u): ";
135 std::getline(std::cin, line);
136 init_x = std::stod(line);
137 std::cout << "Enter initial position.y (pixel v): ";
138 std::getline(std::cin, line);
139 init_y = std::stod(line);
140 } catch (const std::exception &e) {
141 std::cerr << "Invalid input, aborted." << std::endl;
142 rclcpp::shutdown();
143 return 1;
144 }
145
146 auto node = std::make_shared<RelocalizationNode>(map_id, init_x, init_y);
147 rclcpp::spin(node);
148 rclcpp::shutdown();
149 return 0;
150}
使用说明:
ros2 run examples relocate
Enter map_id: 1774430080403
Enter initial position.x (pixel u): 273
Enter initial position.y (pixel v): 200
[INFO] [1772528939.129777359] [relocalization_node]: Published integrated_command
[INFO] [1772528940.130067255] [relocalization_node]: Published relocalization_pose
[INFO] [1772528940.130129806] [relocalization_node]: Waiting for robot pose data (timeout: 30s)...
[INFO] [1772528945.130242770] [relocalization_node]: Received odometry data - Relocalization successful!
需要将机器人移动到地图构建的起始点,然后运行重定位程序
参数获取方式:map_id 与起点像素坐标(position.x / position.y)的获取见 地图标识与坐标的获取。
6.2.29 获取已存储的地图
该示例中用到了get_map,按地图名称获取机器人上已存储的地图数据,包括地图路径、分辨率、宽高及地图ID等信息。
1//
2// Created by agiuser on 2026/3/2.
3//
4
5#include "aimdk_msgs/srv/get_stored_map_by_name.hpp"
6#include "rclcpp/rclcpp.hpp"
7#include <chrono>
8#include <cinttypes>
9#include <iostream>
10#include <memory>
11#include <string>
12
13class MapClient : public rclcpp::Node {
14public:
15 MapClient() : Node("get_map_client") {
16 client_ = this->create_client<aimdk_msgs::srv::GetStoredMapByName>(
17 "/aimdk_5Fmsgs/srv/GetStoredMapByName");
18 }
19
20 void send_request(const std::string &map_name) {
21 while (!client_->wait_for_service(std::chrono::seconds(1))) {
22 if (!rclcpp::ok()) {
23 RCLCPP_ERROR(this->get_logger(),
24 "Interrupted while waiting for service.");
25 return;
26 }
27 RCLCPP_INFO(this->get_logger(),
28 "Service not available, waiting again...");
29 }
30
31 auto request =
32 std::make_shared<aimdk_msgs::srv::GetStoredMapByName::Request>();
33
34 request->header.stamp = this->now();
35 request->header.frame_id = "";
36
37 request->map_name = map_name;
38
39 auto result_future = client_->async_send_request(request);
40
41 // Use a fresh executor to spin, avoiding any pre-existing state.
42 rclcpp::executors::SingleThreadedExecutor exec;
43 exec.add_node(shared_from_this());
44 if (exec.spin_until_future_complete(result_future,
45 std::chrono::seconds(60)) !=
46 rclcpp::FutureReturnCode::SUCCESS) {
47 RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out.");
48 return;
49 }
50
51 auto result = result_future.get();
52
53 if (result->code == 0) {
54 RCLCPP_INFO(this->get_logger(), "Service call succeeded");
55 RCLCPP_INFO(this->get_logger(), "Map path: %s", result->map_path.c_str());
56 RCLCPP_INFO(this->get_logger(), "Map resolution: %f",
57 result->map_info.resolution);
58 RCLCPP_INFO(this->get_logger(), "Map width: %u", result->map_info.width);
59 RCLCPP_INFO(this->get_logger(), "Map height: %u",
60 result->map_info.height);
61 RCLCPP_INFO(this->get_logger(), "Map id: %" PRIu64, result->map_id);
62 } else {
63 RCLCPP_ERROR(this->get_logger(),
64 "Service call failed with code: %" PRIu64, result->code);
65 }
66 }
67
68private:
69 rclcpp::Client<aimdk_msgs::srv::GetStoredMapByName>::SharedPtr client_;
70};
71
72int main(int argc, char **argv) {
73 rclcpp::init(argc, argv);
74
75 auto client = std::make_shared<MapClient>();
76
77 // Map name (NOT map_id) is required. To list stored maps, run:
78 // sqlite3 /agibot/data/var/MapManagerModule/map.db "SELECT * FROM map;"
79 std::cout << "Enter map name: ";
80 std::string map_name;
81 std::getline(std::cin, map_name);
82 if (map_name.empty()) {
83 RCLCPP_ERROR(client->get_logger(), "Map name is empty, aborted.");
84 rclcpp::shutdown();
85 return 1;
86 }
87 client->send_request(map_name);
88
89 rclcpp::shutdown();
90 return 0;
91}
使用说明:
ros2 run examples get_map
Enter map name: test_map
[INFO] [get_map_client]: Service call succeeded
[INFO] [get_map_client]: Map path: /path/to/test_map.png
[INFO] [get_map_client]: Map resolution: 0.050000
[INFO] [get_map_client]: Map width: 200
[INFO] [get_map_client]: Map height: 200
[INFO] [get_map_client]: Map id: 1774430080403
参数获取方式:输入的是地图名称(map_name),不是 map_id;map_name 的获取见 地图标识与坐标的获取。
6.2.31 播放灵创动作
该示例中用到了play_linkcraft,列出机器人上的灵创动作,并且选择一条进行播放
1//
2// Created by agiuser on 2026/3/3.
3//
4
5#include <aimdk_msgs/srv/execute_action_resource.hpp>
6#include <aimdk_msgs/srv/get_robot_resources.hpp>
7#include <cinttypes>
8#include <iostream>
9#include <rclcpp/rclcpp.hpp>
10#include <string>
11#include <vector>
12
13using namespace std::chrono_literals;
14
15class RobotResourceClient : public rclcpp::Node {
16public:
17 RobotResourceClient() : Node("robot_resource_client") {
18 get_resource_client_ =
19 this->create_client<aimdk_msgs::srv::GetRobotResources>(
20 "/aimdk_5Fmsgs/srv/GetRobotResources");
21 play_resource_client_ =
22 this->create_client<aimdk_msgs::srv::ExecuteActionResource>(
23 "/aimdk_5Fmsgs/srv/ExecuteActionResource");
24 }
25
26 bool get_robot_resources() {
27 while (!get_resource_client_->wait_for_service(1s)) {
28 if (!rclcpp::ok()) {
29 return false;
30 }
31 RCLCPP_INFO(this->get_logger(),
32 "Service not available, waiting again...");
33 }
34
35 if (!rclcpp::ok()) {
36 return false;
37 }
38
39 auto request =
40 std::make_shared<aimdk_msgs::srv::GetRobotResources::Request>();
41
42 auto future_result = get_resource_client_->async_send_request(request);
43
44 if (rclcpp::spin_until_future_complete(this->get_node_base_interface(),
45 future_result) !=
46 rclcpp::FutureReturnCode::SUCCESS) {
47 if (rclcpp::ok()) {
48 RCLCPP_ERROR(this->get_logger(), "Failed to call GetRobotResources");
49 }
50 return false;
51 }
52
53 auto response = future_result.get();
54 resources_ = response->robot_resources;
55 RCLCPP_INFO(this->get_logger(), "Found %zu resources", resources_.size());
56 return true;
57 }
58
59 void print_resource_menu() {
60 if (resources_.empty()) {
61 std::cout << "(no resources)\n";
62 return;
63 }
64
65 std::cout << "\n=== Available Robot Resources ===\n";
66 for (size_t i = 0; i < resources_.size(); ++i) {
67 const auto &ver = resources_[i].current_version;
68 std::cout << "[" << i << "] "
69 << (ver.name.empty() ? "<no name>" : ver.name) << "\n";
70 std::cout << " key: " << resources_[i].resource_key << "\n";
71 std::cout << " version: " << ver.version << "\n";
72 }
73 std::cout << "=================================\n\n";
74 }
75
76 int pick_resource() {
77 if (resources_.empty()) {
78 return -1;
79 }
80
81 std::string line;
82 while (true) {
83 std::cout << "Select resource index [0-" << resources_.size() - 1
84 << "] (q to quit): ";
85 if (!std::getline(std::cin, line)) {
86 return -1;
87 }
88 if (line == "q" || line == "quit" || line == "exit") {
89 return -1;
90 }
91 try {
92 int idx = std::stoi(line);
93 if (idx >= 0 && idx < static_cast<int>(resources_.size())) {
94 return idx;
95 }
96 std::cout << "Index out of range (0-" << resources_.size() - 1
97 << ").\n";
98 } catch (...) {
99 std::cout << "Please enter a number.\n";
100 }
101 }
102 }
103
104 void play_resource(int idx) {
105 while (!play_resource_client_->wait_for_service(1s)) {
106 if (!rclcpp::ok()) {
107 RCLCPP_ERROR(this->get_logger(),
108 "Interrupted while waiting for service.");
109 return;
110 }
111 RCLCPP_INFO(this->get_logger(), "Service not available, waiting...");
112 }
113
114 if (!rclcpp::ok()) {
115 return;
116 }
117
118 const auto &resource = resources_[idx];
119 auto request =
120 std::make_shared<aimdk_msgs::srv::ExecuteActionResource::Request>();
121
122 request->resource_key = resource.resource_key;
123 request->resource_version = resource.current_version.version;
124
125 if (request->resource_key.find("onnx") != std::string::npos) {
126 request->meta = R"({"resource_type": "BODY_MONTION"})";
127 } else {
128 request->meta = R"({"resource_type": "ARM_MONTION"})";
129 }
130
131 RCLCPP_INFO(this->get_logger(), "Sending request:");
132 RCLCPP_INFO(this->get_logger(), " resource_key: %s",
133 request->resource_key.c_str());
134 RCLCPP_INFO(this->get_logger(), " resource_version: %s",
135 request->resource_version.c_str());
136 RCLCPP_INFO(this->get_logger(), " meta: %s", request->meta.c_str());
137
138 auto future = play_resource_client_->async_send_request(request);
139
140 if (rclcpp::spin_until_future_complete(this->get_node_base_interface(),
141 future) ==
142 rclcpp::FutureReturnCode::SUCCESS) {
143 handle_response(future.get());
144 } else {
145 if (rclcpp::ok()) {
146 RCLCPP_ERROR(this->get_logger(),
147 "Failed to call ExecuteActionResource");
148 }
149 }
150 }
151
152private:
153 rclcpp::Client<aimdk_msgs::srv::GetRobotResources>::SharedPtr
154 get_resource_client_;
155 rclcpp::Client<aimdk_msgs::srv::ExecuteActionResource>::SharedPtr
156 play_resource_client_;
157 std::vector<aimdk_msgs::msg::RobotResource> resources_;
158
159 void handle_response(
160 const aimdk_msgs::srv::ExecuteActionResource::Response::SharedPtr
161 response) {
162 RCLCPP_INFO(this->get_logger(), "Service response received:");
163 RCLCPP_INFO(this->get_logger(), "Response Header:");
164 RCLCPP_INFO(this->get_logger(), " stamp: %d.%09u",
165 response->header.header.stamp.sec,
166 response->header.header.stamp.nanosec);
167 RCLCPP_INFO(this->get_logger(), " code: %" PRId64,
168 response->header.header.code);
169 RCLCPP_INFO(this->get_logger(), "Status:");
170 RCLCPP_INFO(this->get_logger(), " value: %d",
171 response->header.status.value);
172 RCLCPP_INFO(this->get_logger(), " message: %s",
173 response->header.message.c_str());
174 }
175};
176
177int main(int argc, char **argv) {
178 rclcpp::init(argc, argv);
179 auto client_node = std::make_shared<RobotResourceClient>();
180
181 if (!client_node->get_robot_resources()) {
182 rclcpp::shutdown();
183 return 1;
184 }
185
186 client_node->print_resource_menu();
187 int chosen = client_node->pick_resource();
188 if (chosen < 0) {
189 RCLCPP_INFO(client_node->get_logger(), "No resource selected, exiting.");
190 rclcpp::shutdown();
191 return 0;
192 }
193
194 client_node->play_resource(chosen);
195 rclcpp::shutdown();
196 return 0;
197}
使用说明:
ros2 run examples play_linkcraft
[INFO] [1772529153.517267476] [robot_resource_client]: Found 1 resources
=== Available Robot Resources ===
[0] resource_key: linkcraft_resource_onnx_01KBM2BHNFM93Z0DK6ES9F6DJA
key: linkcraft_resource_onnx_01KBM2BHNFM93Z0DK6ES9F6DJA
version: 0.0.1
=================================
Select resource index [0-0]: 0
[INFO] [1772529153.517488347] [robot_resource_client]: Sending request:
[INFO] [1772529153.517497834] [robot_resource_client]: resource_key: linkcraft_resource_onnx_01KBM2BHNFM93Z0DK6ES9F6DJA
[INFO] [1772529153.517505889] [robot_resource_client]: resource_version: 0.0.1
[INFO] [1772529153.517473238] [robot_resource_client]: meta: {"resource_type": "BODY_MONTION"}
[INFO] [1772529153.553673433] [robot_resource_client]: Service response received:
[INFO] [1772529153.553697021] [robot_resource_client]: Response Header:
[INFO] [1772529153.553700410] [robot_resource_client]: stamp: 0.000000000
[INFO] [1772529153.553704130] [robot_resource_client]: code: 0
[INFO] [1772529153.553707001] [robot_resource_client]: Status:
[INFO] [1772529153.553709853] [robot_resource_client]: value: 1
[INFO] [1772529153.553712662] [robot_resource_client]: message: 1/1 task(s) failed. Details: localhost: failed (status code: 400) - Failed with status code: 400
6.2.32 群控播放灵创动作
该示例中用到了 multi_play_linkcraft,在多台机器人之间进行时间同步,并群控播放灵创动作。
1//
2// Created by agiuser on 2026/3/3.
3//
4
5#include <aimdk_msgs/msg/slave_device.hpp>
6#include <aimdk_msgs/srv/execute_action_resource.hpp>
7#include <aimdk_msgs/srv/get_robot_resources.hpp>
8#include <aimdk_msgs/srv/group_control_time_sync.hpp>
9#include <cinttypes>
10#include <iostream>
11#include <rclcpp/rclcpp.hpp>
12#include <string>
13#include <vector>
14
15using namespace std::chrono_literals;
16
17class MultiRobotResourceClient : public rclcpp::Node {
18public:
19 MultiRobotResourceClient() : Node("robot_resource_client") {
20 get_resource_client_ =
21 this->create_client<aimdk_msgs::srv::GetRobotResources>(
22 "/aimdk_5Fmsgs/srv/GetRobotResources");
23 play_resource_client_ =
24 this->create_client<aimdk_msgs::srv::ExecuteActionResource>(
25 "/aimdk_5Fmsgs/srv/ExecuteActionResource");
26 time_sync_client_ =
27 this->create_client<aimdk_msgs::srv::GroupControlTimeSync>(
28 "/aimdk_5Fmsgs/srv/GroupControlTimeSync");
29 }
30
31 bool get_robot_resources() {
32 while (!get_resource_client_->wait_for_service(1s)) {
33 if (!rclcpp::ok()) {
34 return false;
35 }
36 RCLCPP_INFO(this->get_logger(),
37 "Service not available, waiting again...");
38 }
39
40 if (!rclcpp::ok()) {
41 return false;
42 }
43
44 auto request =
45 std::make_shared<aimdk_msgs::srv::GetRobotResources::Request>();
46
47 auto future_result = get_resource_client_->async_send_request(request);
48
49 if (rclcpp::spin_until_future_complete(this->get_node_base_interface(),
50 future_result) !=
51 rclcpp::FutureReturnCode::SUCCESS) {
52 if (rclcpp::ok()) {
53 RCLCPP_ERROR(this->get_logger(), "Failed to call GetRobotResources");
54 }
55 return false;
56 }
57
58 auto response = future_result.get();
59 resources_ = response->robot_resources;
60 RCLCPP_INFO(this->get_logger(), "Found %zu resources", resources_.size());
61 return true;
62 }
63
64 void print_resource_menu() {
65 if (resources_.empty()) {
66 std::cout << "(no resources)\n";
67 return;
68 }
69
70 std::cout << "\n=== Available Robot Resources ===\n";
71 for (size_t i = 0; i < resources_.size(); ++i) {
72 const auto &ver = resources_[i].current_version;
73 std::cout << "[" << i << "] "
74 << (ver.name.empty() ? "<no name>" : ver.name) << "\n";
75 std::cout << " key: " << resources_[i].resource_key << "\n";
76 std::cout << " version: " << ver.version << "\n";
77 }
78 std::cout << "=================================\n\n";
79 }
80
81 int pick_resource() {
82 if (resources_.empty()) {
83 return -1;
84 }
85
86 std::string line;
87 while (true) {
88 std::cout << "Select resource index [0-" << resources_.size() - 1
89 << "] (q to quit): ";
90 if (!std::getline(std::cin, line)) {
91 return -1;
92 }
93 if (line == "q" || line == "quit" || line == "exit") {
94 return -1;
95 }
96 try {
97 int idx = std::stoi(line);
98 if (idx >= 0 && idx < static_cast<int>(resources_.size())) {
99 return idx;
100 }
101 std::cout << "Index out of range (0-" << resources_.size() - 1
102 << ").\n";
103 } catch (...) {
104 std::cout << "Please enter a number.\n";
105 }
106 }
107 }
108
109 void play_resource(int idx) {
110 while (!play_resource_client_->wait_for_service(1s)) {
111 if (!rclcpp::ok()) {
112 RCLCPP_ERROR(this->get_logger(),
113 "Interrupted while waiting for service.");
114 return;
115 }
116 RCLCPP_INFO(this->get_logger(), "Service not available, waiting...");
117 }
118
119 if (!rclcpp::ok()) {
120 return;
121 }
122
123 const auto &resource = resources_[idx];
124 auto request =
125 std::make_shared<aimdk_msgs::srv::ExecuteActionResource::Request>();
126
127 request->resource_key = resource.resource_key;
128 request->resource_version = resource.current_version.version;
129
130 for (const auto &ip : slave_ips_) {
131 aimdk_msgs::msg::SlaveDevice slave;
132 slave.ip = ip;
133 request->slaves.push_back(slave);
134 }
135
136 if (request->resource_key.find("onnx") != std::string::npos) {
137 request->meta =
138 R"({"control_type": "GROUP_CONTROL","resource_type": "BODY_MONTION"})";
139 } else {
140 request->meta =
141 R"({"control_type": "GROUP_CONTROL","resource_type": "ARM_MONTION"})";
142 }
143
144 RCLCPP_INFO(this->get_logger(), "Sending request:");
145 RCLCPP_INFO(this->get_logger(), " resource_key: %s",
146 request->resource_key.c_str());
147 RCLCPP_INFO(this->get_logger(), " resource_version: %s",
148 request->resource_version.c_str());
149 RCLCPP_INFO(this->get_logger(), " meta: %s", request->meta.c_str());
150 for (size_t i = 0; i < request->slaves.size(); ++i) {
151 RCLCPP_INFO(this->get_logger(), " slaves[%zu] ip: %s", i,
152 request->slaves[i].ip.c_str());
153 }
154
155 auto future = play_resource_client_->async_send_request(request);
156
157 if (rclcpp::spin_until_future_complete(this->get_node_base_interface(),
158 future) ==
159 rclcpp::FutureReturnCode::SUCCESS) {
160 handle_response(future.get());
161 } else {
162 if (rclcpp::ok()) {
163 RCLCPP_ERROR(this->get_logger(),
164 "Failed to call ExecuteActionResource");
165 }
166 }
167 }
168
169 bool sync_time(const std::vector<std::string> &ips) {
170 slave_ips_ = ips;
171
172 while (!time_sync_client_->wait_for_service(1s)) {
173 if (!rclcpp::ok()) {
174 return false;
175 }
176 RCLCPP_INFO(this->get_logger(),
177 "TimeSync service not available, waiting again...");
178 }
179
180 if (!rclcpp::ok()) {
181 return false;
182 }
183
184 auto request =
185 std::make_shared<aimdk_msgs::srv::GroupControlTimeSync::Request>();
186
187 for (const auto &ip : ips) {
188 aimdk_msgs::msg::SlaveDevice slave;
189 slave.ip = ip;
190 request->slaves.push_back(slave);
191 }
192
193 RCLCPP_INFO(this->get_logger(), "Sending GroupControlTimeSync request:");
194 for (const auto &slave : request->slaves) {
195 RCLCPP_INFO(this->get_logger(), " slave ip: %s", slave.ip.c_str());
196 }
197
198 auto future = time_sync_client_->async_send_request(request);
199
200 if (rclcpp::spin_until_future_complete(this->get_node_base_interface(),
201 future) !=
202 rclcpp::FutureReturnCode::SUCCESS) {
203 if (rclcpp::ok()) {
204 RCLCPP_ERROR(this->get_logger(), "Failed to call GroupControlTimeSync");
205 }
206 return false;
207 }
208
209 auto response = future.get();
210 RCLCPP_INFO(this->get_logger(), "GroupControlTimeSync response:");
211 RCLCPP_INFO(this->get_logger(), " error_code: %d", response->error_code);
212 RCLCPP_INFO(this->get_logger(), " error_msg: %s",
213 response->error_msg.c_str());
214 return response->error_code == 0;
215 }
216
217private:
218 rclcpp::Client<aimdk_msgs::srv::GetRobotResources>::SharedPtr
219 get_resource_client_;
220 rclcpp::Client<aimdk_msgs::srv::ExecuteActionResource>::SharedPtr
221 play_resource_client_;
222 rclcpp::Client<aimdk_msgs::srv::GroupControlTimeSync>::SharedPtr
223 time_sync_client_;
224 std::vector<aimdk_msgs::msg::RobotResource> resources_;
225 std::vector<std::string> slave_ips_;
226
227 void handle_response(
228 const aimdk_msgs::srv::ExecuteActionResource::Response::SharedPtr
229 response) {
230 RCLCPP_INFO(this->get_logger(), "Service response received:");
231 RCLCPP_INFO(this->get_logger(), "Response Header:");
232 RCLCPP_INFO(this->get_logger(), " stamp: %d.%09u",
233 response->header.header.stamp.sec,
234 response->header.header.stamp.nanosec);
235 RCLCPP_INFO(this->get_logger(), " code: %" PRId64,
236 response->header.header.code);
237 RCLCPP_INFO(this->get_logger(), "Status:");
238 RCLCPP_INFO(this->get_logger(), " value: %d",
239 response->header.status.value);
240 RCLCPP_INFO(this->get_logger(), " message: %s",
241 response->header.message.c_str());
242 }
243};
244
245int main(int argc, char **argv) {
246 rclcpp::init(argc, argv);
247 auto client_node = std::make_shared<MultiRobotResourceClient>();
248
249 if (!client_node->get_robot_resources()) {
250 rclcpp::shutdown();
251 return 1;
252 }
253
254 // [USER_CONFIG] Replace the IPs below with your slave robot IPs.
255 std::vector<std::string> slave_ips = {"172.22.133.178", "172.22.135.45"};
256 if (!client_node->sync_time(slave_ips)) {
257 RCLCPP_WARN(client_node->get_logger(),
258 "Time sync failed or returned error.");
259 }
260
261 client_node->print_resource_menu();
262 int chosen = client_node->pick_resource();
263 if (chosen < 0) {
264 RCLCPP_INFO(client_node->get_logger(), "No resource selected, exiting.");
265 rclcpp::shutdown();
266 return 0;
267 }
268
269 client_node->play_resource(chosen);
270 rclcpp::shutdown();
271 return 0;
272}
使用说明:
ros2 run examples multi_play_linkcraft
[INFO] [1772529153.517267476] [robot_resource_client]: Found 1 resources
[INFO] [1772529153.517473238] [robot_resource_client]: Sending GroupControlTimeSync request:
[INFO] [1772529153.517488347] [robot_resource_client]: slave ip: 172.22.133.178
[INFO] [1772529153.517497834] [robot_resource_client]: slave ip: 172.22.135.45
[INFO] [1772529153.517505889] [robot_resource_client]: GroupControlTimeSync response:
[INFO] [1772529153.517506000] [robot_resource_client]: error_code: 0
[INFO] [1772529153.517506100] [robot_resource_client]: error_msg: ok
[INFO] [1772529153.517473238] [robot_resource_client]: Sending request:
[INFO] [1772529153.517488347] [robot_resource_client]: resource_key: linkcraft_resource_onnx_01KBM2BHNFM93Z0DK6ES9F6DJA
[INFO] [1772529153.517497834] [robot_resource_client]: resource_version: 0.0.1
[INFO] [1772529153.517505889] [robot_resource_client]: meta: {"control_type": "GROUP_CONTROL","resource_type": "BODY_MONTION"}
[INFO] [1772529153.517506200] [robot_resource_client]: slaves[0] ip: 172.22.133.178
[INFO] [1772529153.517506300] [robot_resource_client]: slaves[1] ip: 172.22.135.45
[INFO] [1772529153.553673433] [robot_resource_client]: Service response received:
[INFO] [1772529153.553697021] [robot_resource_client]: Response Header:
[INFO] [1772529153.553700410] [robot_resource_client]: stamp: 0.000000000
[INFO] [1772529153.553704130] [robot_resource_client]: code: 0
[INFO] [1772529153.553707001] [robot_resource_client]: Status:
[INFO] [1772529153.553709853] [robot_resource_client]: value: 1
[INFO] [1772529153.553712662] [robot_resource_client]: message: 1/1 task(s) failed. Details: localhost: failed (status code: 400) - Failed with status code: 400
6.2.33 LED灯带控制
功能说明:演示如何控制机器人的LED灯带,支持多种显示模式和自定义颜色。
注意
使用本功能前,请在运控计算单元(PC1)上执行以下命令关闭 task_manager 模块,避免其与二开程序争抢灯带控制权:
aima em stop-app task_manager
核心代码:
1#include <aimdk_msgs/msg/common_request.hpp>
2#include <aimdk_msgs/srv/set_pmu_led.hpp>
3#include <algorithm>
4#include <cctype>
5#include <chrono>
6#include <iostream>
7#include <memory>
8#include <rclcpp/rclcpp.hpp>
9#include <signal.h>
10#include <string>
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 PlayLightsClient : public rclcpp::Node {
25public:
26 PlayLightsClient() : Node("play_lights_client") {
27 client_ = this->create_client<aimdk_msgs::srv::SetPmuLed>(
28 "/aimdk_5Fmsgs/srv/SetPmuLed");
29 RCLCPP_INFO(this->get_logger(), "✅ PlayLights client node started.");
30
31 // Wait for the service to become available
32 while (!client_->wait_for_service(std::chrono::seconds(2))) {
33 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
34 }
35 RCLCPP_INFO(this->get_logger(),
36 "🟢 Service available, ready to send request.");
37 }
38
39 bool send_request(uint8_t led_mode, uint8_t r, uint8_t g, uint8_t b,
40 int32_t priority = 6, bool reset_priority = false) {
41 try {
42 auto request = std::make_shared<aimdk_msgs::srv::SetPmuLed::Request>();
43
44 request->led_strip_mode = led_mode;
45 request->r = r;
46 request->g = g;
47 request->b = b;
48 request->priority = priority;
49 request->reset_priority = reset_priority;
50 request->trace_id = "play_lights";
51
52 RCLCPP_INFO(this->get_logger(),
53 "📨 Sending request to control led strip: mode=%hhu, "
54 "RGB=(%hhu, %hhu, %hhu), priority=%d, reset_priority=%s",
55 led_mode, r, g, b, priority,
56 reset_priority ? "true" : "false");
57
58 // LED strip is slow to response (up to ~5s)
59 const std::chrono::milliseconds timeout(5000);
60 for (int i = 0; i < 4; i++) {
61 request->request.header.stamp = this->now();
62 auto future = client_->async_send_request(request);
63 auto retcode = rclcpp::spin_until_future_complete(
64 this->shared_from_this(), future, timeout);
65
66 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
67 // retry as remote peer is NOT handled well by ROS
68 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
69 continue;
70 }
71 // future.done
72 auto response = future.get();
73 if (response->status_code == 0) {
74 RCLCPP_INFO(this->get_logger(),
75 "✅ LED strip command sent successfully.");
76 return true;
77 } else {
78 // 0x1024: priority insufficient (priority < current threshold);
79 // use a higher priority or set reset_priority=true to reset the
80 // threshold to 0.
81 RCLCPP_ERROR(this->get_logger(),
82 "❌ LED strip command failed with status: 0x%X (%s)",
83 response->status_code,
84 response->status_code == 0x1024 ? "priority insufficient"
85 : "failure");
86 return false;
87 }
88 }
89 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
90 return false;
91 } catch (const std::exception &e) {
92 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
93 return false;
94 }
95 }
96
97private:
98 rclcpp::Client<aimdk_msgs::srv::SetPmuLed>::SharedPtr client_;
99};
100
101int main(int argc, char **argv) {
102 try {
103 rclcpp::init(argc, argv);
104 signal(SIGINT, signal_handler);
105 signal(SIGTERM, signal_handler);
106
107 g_node = std::make_shared<PlayLightsClient>();
108 auto client_node = std::dynamic_pointer_cast<PlayLightsClient>(g_node);
109
110 int led_mode = 0; // LED Strip Mode
111 int r = 255, g = 0, b = 0; // RGB values
112 int priority = 6; // Priority
113 bool reset_priority = false;
114
115 // Parse command-line arguments: mode R G B priority reset_priority
116 // e.g. ros2 run examples play_lights 1 255 0 0 6 false
117 if (argc > 6) {
118 led_mode = std::stoi(argv[1]);
119 if (led_mode < 0 || led_mode > 3) {
120 throw std::runtime_error("invalid mode");
121 }
122 r = std::stoi(argv[2]);
123 if (r < 0 || r > 255) {
124 throw std::runtime_error("invalid R value");
125 }
126 g = std::stoi(argv[3]);
127 if (g < 0 || g > 255) {
128 throw std::runtime_error("invalid G value");
129 }
130 b = std::stoi(argv[4]);
131 if (b < 0 || b > 255) {
132 throw std::runtime_error("invalid B value");
133 }
134 priority = std::stoi(argv[5]);
135 if (priority < 0) {
136 throw std::runtime_error("invalid priority value");
137 }
138 std::string reset_str = argv[6];
139 std::transform(reset_str.begin(), reset_str.end(), reset_str.begin(),
140 ::tolower);
141 reset_priority = (reset_str == "true" || reset_str == "1" ||
142 reset_str == "yes" || reset_str == "y");
143 } else {
144 std::cout << "=== LED strip control example ===" << std::endl;
145 std::cout << "Select LED strip mode:" << std::endl;
146 std::cout << "0 - Steady on" << std::endl;
147 std::cout << "1 - Breathing (4s cycle, sine brightness)" << std::endl;
148 std::cout << "2 - Blinking (1s cycle, 0.5s on, 0.5s off)" << std::endl;
149 std::cout << "3 - Flowing (2s cycle, light up from left to right)"
150 << std::endl;
151 std::cout << "Enter mode (0-3): ";
152 std::cin >> led_mode;
153 if (led_mode < 0 || led_mode > 3) {
154 throw std::runtime_error("invalid mode");
155 }
156
157 std::cout << "\nSet RGB color values (0-255):" << std::endl;
158 std::cout << "Red (R): ";
159 std::cin >> r;
160 if (r < 0 || r > 255) {
161 throw std::runtime_error("invalid R value");
162 }
163 std::cout << "Green (G): ";
164 std::cin >> g;
165 if (g < 0 || g > 255) {
166 throw std::runtime_error("invalid G value");
167 }
168 std::cout << "Blue (B): ";
169 std::cin >> b;
170 if (b < 0 || b > 255) {
171 throw std::runtime_error("invalid B value");
172 }
173
174 std::cout
175 << "\nSet priority (higher value = higher priority, default=6): ";
176 std::cin >> priority;
177 if (priority < 0) {
178 throw std::runtime_error("invalid priority value");
179 }
180
181 std::string reset_input;
182 std::cout << "Reset priority after command? (y/n, default=n): ";
183 std::cin >> reset_input;
184 std::transform(reset_input.begin(), reset_input.end(),
185 reset_input.begin(), ::tolower);
186 reset_priority = (reset_input == "true" || reset_input == "1" ||
187 reset_input == "yes" || reset_input == "y");
188 }
189
190 if (client_node) {
191 client_node->send_request(led_mode, r, g, b, priority, reset_priority);
192 }
193
194 g_node.reset();
195 rclcpp::shutdown();
196
197 return 0;
198 } catch (const std::exception &e) {
199 RCLCPP_ERROR(rclcpp::get_logger("main"),
200 "Program terminated with exception: %s", e.what());
201 return 1;
202 }
203}
使用说明:
# 构建
colcon build --packages-select examples
# 交互式运行
ros2 run examples play_lights
# 命令行参数运行: <模式> <R> <G> <B> <优先级> <重置优先级(true/false)>
ros2 run examples play_lights 1 255 0 0 6 false # 模式1,红色,优先级6
输出示例:
=== LED strip control example ===
Select LED strip mode:
0 - Steady on
1 - Breathing (4s cycle, sine brightness)
2 - Blinking (1s cycle, 0.5s on, 0.5s off)
3 - Flowing (2s cycle, light up from left to right)
Enter mode (0-3): 1
Set RGB color values (0-255):
Red (R): 255
Green (G): 0
Blue (B): 0
Set priority (higher value = higher priority, default=6): 6
Reset priority after command? (y/n, default=n): n
[INFO] [play_lights_client]: 📨 Sending request to control led strip: mode=1, RGB=(255, 0, 0), priority=6, reset_priority=false
[INFO] [play_lights_client]: ✅ LED strip command sent successfully.
技术特点:
支持4种LED显示模式
RGB颜色自定义
优先级控制(值越大优先级越高)
优先级重置功能
异步服务调用
命令行参数支持
输入参数验证
友好的用户交互界面
6.2.34 GNSS定位数据订阅
该示例中用到了echo_gnss_data,通过订阅GNSS话题获取定位数据,输出经纬度、海拔和协方差信息。
功能特点:
订阅NavSatFix定位数据(经纬度、海拔)
实时显示定位状态和协方差
话题说明:
/aima/hal/sensor/gnss: 定位数据 (sensor_msgs/NavSatFix),由系统自动发布
1#include <chrono>
2#include <iomanip>
3#include <rclcpp/rclcpp.hpp>
4#include <sensor_msgs/msg/nav_sat_fix.hpp>
5#include <sstream>
6#include <string>
7
8class GnssPrinter : public rclcpp::Node {
9public:
10 GnssPrinter() : rclcpp::Node("gnss_printer") {
11 constexpr char kNavSatTopic[] = "/aima/hal/sensor/gnss";
12
13 auto qos = rclcpp::QoS(rclcpp::KeepLast(10))
14 .reliability(rclcpp::ReliabilityPolicy::Reliable)
15 .durability(rclcpp::DurabilityPolicy::TransientLocal);
16
17 navsat_sub_ = this->create_subscription<sensor_msgs::msg::NavSatFix>(
18 kNavSatTopic, qos,
19 std::bind(&GnssPrinter::navsat_cb, this, std::placeholders::_1));
20
21 RCLCPP_INFO(this->get_logger(), "Subscribing NavSatFix: %s", kNavSatTopic);
22 }
23
24private:
25 void navsat_cb(const sensor_msgs::msg::NavSatFix::SharedPtr msg) {
26 auto now = std::chrono::steady_clock::now();
27 double dt_ms =
28 last_navsat_recv_.time_since_epoch().count() == 0
29 ? 0.0
30 : std::chrono::duration<double, std::milli>(now - last_navsat_recv_)
31 .count();
32 last_navsat_recv_ = now;
33
34 std::ostringstream oss;
35 oss << std::fixed << std::setprecision(6);
36 oss << "\n========== NavSatFix ==========\n";
37 oss << "stamp=" << rclcpp::Time(msg->header.stamp).seconds() << "s\n";
38 oss << "frame=" << msg->header.frame_id << "\n";
39 oss << "recv_dt=" << std::setprecision(3) << dt_ms << "ms\n";
40 oss << std::setprecision(8);
41 oss << "latitude: " << msg->latitude << " deg\n";
42 oss << "longitude: " << msg->longitude << " deg\n";
43 oss << std::setprecision(3);
44 oss << "altitude: " << msg->altitude << " m\n";
45
46 const char *status_str = "UNKNOWN";
47 switch (msg->status.status) {
48 case sensor_msgs::msg::NavSatStatus::STATUS_NO_FIX:
49 status_str = "NO_FIX";
50 break;
51 case sensor_msgs::msg::NavSatStatus::STATUS_FIX:
52 status_str = "FIX";
53 break;
54 }
55 oss << "status: " << status_str << "\n";
56 oss << "service: " << msg->status.service << "\n";
57
58 oss << std::setprecision(4);
59 oss << "position_covariance: [";
60 for (size_t i = 0; i < 9; ++i) {
61 if (i > 0)
62 oss << ", ";
63 oss << msg->position_covariance[i];
64 }
65 oss << "]\n";
66 oss << "position_covariance_type: "
67 << static_cast<int>(msg->position_covariance_type);
68
69 RCLCPP_INFO(this->get_logger(), "%s", oss.str().c_str());
70 }
71
72 rclcpp::Subscription<sensor_msgs::msg::NavSatFix>::SharedPtr navsat_sub_;
73 std::chrono::steady_clock::time_point last_navsat_recv_{};
74};
75
76int main(int argc, char **argv) {
77 rclcpp::init(argc, argv);
78 rclcpp::spin(std::make_shared<GnssPrinter>());
79 rclcpp::shutdown();
80 return 0;
81}
使用说明:
# 构建
colcon build --packages-select examples
# 订阅GNSS数据
ros2 run examples echo_gnss_data
输出示例:
[INFO] [gnss_printer]: Subscribing NavSatFix: /aima/hal/sensor/gnss
========== NavSatFix ==========
stamp=1234567890.123456s
frame=gnss
recv_dt=1000.000ms
latitude: 31.84537000 deg
longitude: 117.19882000 deg
altitude: 30.000 m
status: FIX
service: 1
position_covariance: [1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000]
position_covariance_type: 2
注意事项:
定位状态说明:NO_FIX(无定位)、FIX(单点定位)
室内环境下 status 通常为 NO_FIX,需到室外开阔处等待定位
6.2.35 上肢控制
备注
运行前需先切换到对应模式,详见 上肢控制。
1// Upper body control example.
2//
3// Usage:
4// ros2 run examples upper_body_control <mode>
5//
6// mode:
7// head — head center pose, requires HEAD_ONLY mode
8// claw — claw half-open, requires UPPERBODY_REMOTE_SPLIT mode
9// joint — dexterous hand half-open, requires UPPERBODY_REMOTE_SPLIT mode
10// gesture — dexterous hand gesture 1, requires UPPERBODY_REMOTE_SPLIT mode
11//
12// Switch to the desired MC mode before running, e.g. for head:
13// ros2 run examples set_mc_action SD # → STAND_DEFAULT
14// ros2 run examples set_mc_action HO # → HEAD_ONLY
15// or for claw / joint / gesture:
16// ros2 run examples set_mc_action URS # → UPPERBODY_REMOTE_SPLIT
17
18#include <array>
19#include <chrono>
20#include <cstdint>
21#include <cstdio>
22#include <map>
23#include <memory>
24#include <string>
25#include <vector>
26
27#include "aimdk_msgs/msg/message_header.hpp"
28#include "aimdk_msgs/msg/upper_body_command_array.hpp"
29#include "rclcpp/rclcpp.hpp"
30
31using namespace std::chrono_literals;
32using Msg = aimdk_msgs::msg::UpperBodyCommandArray;
33
34// hand_sub_mode, head_pos, arm_pos, hand_pos
35struct ModeConfig {
36 uint8_t hand_sub_mode;
37 std::array<double, 2> head_pos;
38 std::array<double, 14> arm_pos;
39 std::vector<double> hand_pos;
40};
41
42static const std::map<std::string, ModeConfig> &modes() {
43 static const std::map<std::string, ModeConfig> kModes = {
44 // head_yaw=0, head_pitch=0 (center)
45 {"head",
46 {0,
47 {{0.0, 0.0}},
48 {{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
49 0.0}},
50 {}}},
51 // left_open=0.5, right_open=0.5
52 {"claw",
53 {Msg::HAND_CLAW_OPEN_CLOSE, // claw (1)
54 {{0.0, 0.0}},
55 {{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
56 0.0}},
57 {0.5, 0.5}}},
58 // left 10 joints + right 10 joints, in rad, all half-open
59 // (each joint at half its URDF range limit). Right is the mirror of left.
60 // Joint order: [thumb_roll, thumb_abad, thumb_mcp, index_abad, index_pip,
61 // middle_pip, ring_abad, ring_pip, pinky_abad, pinky_pip]
62 {"joint",
63 {Msg::HAND_DEXTEROUS_JOINT, // dexterous joint (2)
64 {{0.0, 0.0}},
65 {{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
66 0.0}},
67 {-0.6, 0.8, -0.4, 0.1, 0.7, 0.7, -0.1, 0.7, -0.1, 0.7,
68 0.6, -0.8, 0.4, -0.1, 0.7, 0.7, 0.1, 0.7, 0.1, 0.7}}},
69 // left_gesture=1, left_open=1.0, right_gesture=1, right_open=1.0
70 {"gesture",
71 {Msg::HAND_DEXTEROUS_GESTURE, // dexterous gesture (3)
72 {{0.0, 0.0}},
73 {{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
74 0.0}},
75 {1.0, 1.0, 1.0, 1.0}}},
76 };
77 return kModes;
78}
79
80class UpperBodyControlNode : public rclcpp::Node {
81public:
82 UpperBodyControlNode(const std::string &mode)
83 : Node("upper_body_control"), seq_(0) {
84 const auto &cfg = modes().at(mode);
85 hand_sub_mode_ = cfg.hand_sub_mode;
86 head_pos_ = cfg.head_pos;
87 arm_pos_ = cfg.arm_pos;
88 hand_pos_ = cfg.hand_pos;
89 pub_ = create_publisher<Msg>("/mc/upper_body_command", 10);
90 timer_ = create_wall_timer(20ms, [this]() { publish(); });
91 RCLCPP_INFO(get_logger(), "mode=%s hand_sub_mode=%d", mode.c_str(),
92 hand_sub_mode_);
93 }
94
95private:
96 void publish() {
97 Msg msg;
98 auto t = this->now();
99 msg.header.stamp.sec = static_cast<int32_t>(t.seconds());
100 msg.header.stamp.nanosec =
101 static_cast<uint32_t>(t.nanoseconds() % 1000000000);
102 msg.header.frame_id = "mc_upper_body";
103 msg.header.sequence = seq_++;
104 msg.source = "upper_body_example";
105 msg.hand_sub_mode = hand_sub_mode_;
106 msg.head_pos = head_pos_;
107 msg.arm_pos = arm_pos_;
108 msg.hand_pos = hand_pos_;
109 pub_->publish(msg);
110 }
111
112 rclcpp::Publisher<Msg>::SharedPtr pub_;
113 rclcpp::TimerBase::SharedPtr timer_;
114 uint32_t seq_;
115 uint8_t hand_sub_mode_;
116 std::array<double, 2> head_pos_;
117 std::array<double, 14> arm_pos_;
118 std::vector<double> hand_pos_;
119};
120
121int main(int argc, char **argv) {
122 rclcpp::init(argc, argv);
123
124 std::string mode;
125 if (argc >= 2)
126 mode = argv[1];
127
128 if (modes().find(mode) == modes().end()) {
129 std::string usage = "Usage: upper_body_control <";
130 bool first = true;
131 for (const auto &[k, _] : modes()) {
132 if (!first)
133 usage += "|";
134 usage += k;
135 first = false;
136 }
137 usage += ">";
138 std::printf("%s\n", usage.c_str());
139 rclcpp::shutdown();
140 return 1;
141 }
142
143 rclcpp::spin(std::make_shared<UpperBodyControlNode>(mode));
144 rclcpp::shutdown();
145 return 0;
146}
使用说明:
colcon build --packages-select examples
# 头部控制(HEAD_ONLY 模式)
ros2 run examples upper_body_control head
# 夹爪控制(claw,UPPERBODY_REMOTE_SPLIT 模式)
ros2 run examples upper_body_control claw
# 灵巧手关节(UPPERBODY_REMOTE_SPLIT 模式)
ros2 run examples upper_body_control joint
# 灵巧手手势(UPPERBODY_REMOTE_SPLIT 模式)
ros2 run examples upper_body_control gesture
6.2.36 订阅 HDS 诊断信息 / 查询历史事件
订阅 /aima/hds/diag_code_list、/aima/hds/alert_code_list、/aima/hds/post_proc/signal 三个话题实时获取诊断码、告警码与后处理信号;同时支持调用 GetHdsEventHistory 服务查询历史事件。完整的诊断码清单见 诊断码参考表。
1// HDS Diagnostics Tool — Subscribe to HDS topics or query history events.
2//
3// See docs/cn/example/Cpp.md section "Subscribe HDS diagnostics /
4// query history events" for full usage and field descriptions.
5
6#include <chrono>
7#include <cstdint>
8#include <functional>
9#include <iomanip>
10#include <iostream>
11#include <memory>
12#include <sstream>
13#include <string>
14#include <vector>
15
16#include <rclcpp/rclcpp.hpp>
17
18#include "aimdk_msgs/msg/alert_code_array.hpp"
19#include "aimdk_msgs/msg/diagnostic_info_array.hpp"
20#include "aimdk_msgs/msg/diagnostic_type.hpp"
21#include "aimdk_msgs/msg/hds_event_query.hpp"
22#include "aimdk_msgs/msg/post_proc_sig_array.hpp"
23#include "aimdk_msgs/srv/get_hds_event_history.hpp"
24
25using namespace std::chrono_literals;
26
27namespace {
28
29std::string DiagTypeName(int32_t value) {
30 switch (value) {
31 case aimdk_msgs::msg::DiagnosticType::NORMAL:
32 return "NORMAL";
33 case aimdk_msgs::msg::DiagnosticType::TRIGGER_APPEAR:
34 return "APPEAR";
35 case aimdk_msgs::msg::DiagnosticType::TRIGGER_DISAPPEAR:
36 return "DISAPPEAR";
37 default:
38 return "UNKNOWN(" + std::to_string(value) + ")";
39 }
40}
41
42std::string StateName(uint8_t value) {
43 switch (value) {
44 case aimdk_msgs::msg::HdsEventQuery::STATE_ALL:
45 return "ALL";
46 case aimdk_msgs::msg::HdsEventQuery::STATE_ACTIVE:
47 return "ACTIVE";
48 case aimdk_msgs::msg::HdsEventQuery::STATE_RECOVERED:
49 return "RECOVERED";
50 default:
51 return "UNKNOWN";
52 }
53}
54
55std::string HexCode(uint64_t code, int width = 10) {
56 std::ostringstream oss;
57 oss << "0x" << std::hex << std::uppercase << std::setw(width)
58 << std::setfill('0') << code;
59 return oss.str();
60}
61
62uint64_t ParseHex(const std::string &s) { return std::stoull(s, nullptr, 0); }
63
64// ---------- echo mode ----------
65
66class HdsDiagnosticsEcho : public rclcpp::Node {
67public:
68 HdsDiagnosticsEcho() : Node("hds_diagnostics_echo") {
69 // HDS publishers use BEST_EFFORT; match it to receive messages.
70 auto qos = rclcpp::QoS(10).best_effort().durability_volatile();
71
72 sub_diag_ = create_subscription<aimdk_msgs::msg::DiagnosticInfoArray>(
73 "/aima/hds/diag_code_list", qos,
74 std::bind(&HdsDiagnosticsEcho::OnDiagnostics, this,
75 std::placeholders::_1));
76
77 sub_alert_ = create_subscription<aimdk_msgs::msg::AlertCodeArray>(
78 "/aima/hds/alert_code_list", qos,
79 std::bind(&HdsDiagnosticsEcho::OnAlerts, this, std::placeholders::_1));
80
81 sub_post_ = create_subscription<aimdk_msgs::msg::PostProcSigArray>(
82 "/aima/hds/post_proc/signal", qos,
83 std::bind(&HdsDiagnosticsEcho::OnPostProc, this,
84 std::placeholders::_1));
85
86 RCLCPP_INFO(get_logger(), "Subscribed: diag_code_list / alert_code_list / "
87 "post_proc/signal");
88 }
89
90private:
91 void
92 OnDiagnostics(const aimdk_msgs::msg::DiagnosticInfoArray::SharedPtr msg) {
93 if (msg->diagnostics.empty())
94 return;
95 std::ostringstream oss;
96 oss << "[Diagnostics] count=" << msg->diagnostics.size();
97 for (const auto &d : msg->diagnostics) {
98 oss << "\n diag_code=" << HexCode(d.diag_code)
99 << " type=" << DiagTypeName(d.type.value)
100 << " module_id=" << static_cast<int>(d.module_id)
101 << " ts=" << d.timestamp << " info=\"" << d.info << "\"";
102 }
103 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
104 }
105
106 void OnAlerts(const aimdk_msgs::msg::AlertCodeArray::SharedPtr msg) {
107 if (msg->list.empty())
108 return;
109 std::ostringstream oss;
110 oss << "[Alerts] count=" << msg->list.size();
111 for (const auto &a : msg->list) {
112 oss << "\n code=\"" << a.code << "\" ts=" << a.timestamp
113 << " first_ts=" << a.first_timestamp;
114 }
115 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
116 }
117
118 void OnPostProc(const aimdk_msgs::msg::PostProcSigArray::SharedPtr msg) {
119 if (msg->post_proc_sig.empty())
120 return;
121 std::ostringstream oss;
122 oss << "[PostProc] count=" << msg->post_proc_sig.size();
123 for (const auto &p : msg->post_proc_sig) {
124 const auto &s = p.sig_set;
125 oss << "\n diag_code=" << HexCode(p.diag_code) << " ts=" << p.timestamp
126 << " robot=" << s.robot_sig
127 << " motion=" << static_cast<int>(s.motion_sig)
128 << " sound=" << static_cast<int>(s.sound_sig)
129 << " led=" << static_cast<int>(s.led_sig)
130 << " facial=" << static_cast<int>(s.facial_sig)
131 << " funcdisable=" << HexCode(s.funcdisable_sig, 16);
132 }
133 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
134 }
135
136 rclcpp::Subscription<aimdk_msgs::msg::DiagnosticInfoArray>::SharedPtr
137 sub_diag_;
138 rclcpp::Subscription<aimdk_msgs::msg::AlertCodeArray>::SharedPtr sub_alert_;
139 rclcpp::Subscription<aimdk_msgs::msg::PostProcSigArray>::SharedPtr sub_post_;
140};
141
142int RunEcho(int argc, char **argv) {
143 rclcpp::init(argc, argv);
144 rclcpp::spin(std::make_shared<HdsDiagnosticsEcho>());
145 rclcpp::shutdown();
146 return 0;
147}
148
149// ---------- history mode ----------
150
151struct HistoryArgs {
152 uint64_t start_ns = 0;
153 uint64_t end_ns = 0;
154 std::vector<uint64_t> diag_codes;
155 std::vector<std::string> alert_codes;
156 uint8_t state = aimdk_msgs::msg::HdsEventQuery::STATE_ALL;
157 bool desc = false;
158 uint32_t offset = 0;
159 uint32_t limit = 20;
160};
161
162bool ParseHistoryArgs(int argc, char **argv, HistoryArgs *args) {
163 // argv[0] = exe, argv[1] = "history", real options start at 2.
164 for (int i = 2; i < argc; ++i) {
165 std::string a = argv[i];
166 auto need_value = [&](const char *flag) -> const char * {
167 if (i + 1 >= argc) {
168 std::cerr << "Missing value for " << flag << std::endl;
169 return nullptr;
170 }
171 return argv[++i];
172 };
173
174 if (a == "--start-ns") {
175 const char *v = need_value("--start-ns");
176 if (!v)
177 return false;
178 args->start_ns = std::stoull(v);
179 } else if (a == "--end-ns") {
180 const char *v = need_value("--end-ns");
181 if (!v)
182 return false;
183 args->end_ns = std::stoull(v);
184 } else if (a == "--state") {
185 const char *v = need_value("--state");
186 if (!v)
187 return false;
188 args->state = static_cast<uint8_t>(std::stoi(v));
189 } else if (a == "--offset") {
190 const char *v = need_value("--offset");
191 if (!v)
192 return false;
193 args->offset = static_cast<uint32_t>(std::stoul(v));
194 } else if (a == "--limit") {
195 const char *v = need_value("--limit");
196 if (!v)
197 return false;
198 args->limit = static_cast<uint32_t>(std::stoul(v));
199 } else if (a == "--desc") {
200 args->desc = true;
201 } else if (a == "--diag-codes") {
202 while (i + 1 < argc && std::string(argv[i + 1]).rfind("--", 0) != 0) {
203 args->diag_codes.push_back(ParseHex(argv[++i]));
204 }
205 } else if (a == "--alert-codes") {
206 while (i + 1 < argc && std::string(argv[i + 1]).rfind("--", 0) != 0) {
207 args->alert_codes.emplace_back(argv[++i]);
208 }
209 } else {
210 std::cerr << "Unknown option: " << a << std::endl;
211 return false;
212 }
213 }
214 return true;
215}
216
217void PrintRecords(
218 const aimdk_msgs::srv::GetHdsEventHistory::Response &response) {
219 std::cout << "total_count = " << response.total_count
220 << ", returned = " << response.records.size()
221 << ", has_more = " << (response.has_more ? "true" : "false")
222 << std::endl;
223 if (response.records.empty())
224 return;
225
226 std::string sep(80, '-');
227 std::cout << sep << std::endl;
228 for (const auto &r : response.records) {
229 double duration_s = static_cast<double>(r.duration_ns) / 1e9;
230 std::cout << "id=" << r.id << " diag=" << HexCode(r.diag_code)
231 << " alert=\"" << r.alert_code << "\"" << std::endl;
232 std::cout << " first_ts=" << r.first_timestamp
233 << " last_ts=" << r.last_timestamp << std::endl;
234 std::cout << " recovered=" << (r.recovered ? "YES" : "NO")
235 << " recover_ts=" << r.recover_timestamp
236 << " duration=" << std::fixed << std::setprecision(3)
237 << duration_s << "s" << std::endl;
238 if (!r.alert_location.empty()) {
239 std::cout << " location=\"" << r.alert_location << "\"" << std::endl;
240 }
241 std::cout << sep << std::endl;
242 }
243}
244
245int RunHistory(int argc, char **argv) {
246 HistoryArgs args;
247 if (!ParseHistoryArgs(argc, argv, &args))
248 return 1;
249
250 rclcpp::init(argc, argv);
251 auto node = std::make_shared<rclcpp::Node>("hds_history_client");
252 auto client = node->create_client<aimdk_msgs::srv::GetHdsEventHistory>(
253 "/aimdk_5Fmsgs/srv/GetHdsEventHistory");
254
255 if (!client->wait_for_service(5s)) {
256 RCLCPP_ERROR(node->get_logger(),
257 "Service /aimdk_5Fmsgs/srv/GetHdsEventHistory not available");
258 rclcpp::shutdown();
259 return 1;
260 }
261
262 auto request =
263 std::make_shared<aimdk_msgs::srv::GetHdsEventHistory::Request>();
264 auto now = node->now();
265 request->header.stamp.sec = now.seconds();
266 request->header.stamp.nanosec = now.nanoseconds() % 1000000000;
267 request->query.start_time = args.start_ns;
268 request->query.end_time = args.end_ns;
269 request->query.diag_codes = args.diag_codes;
270 request->query.alert_codes = args.alert_codes;
271 request->query.state = args.state;
272 request->query.order_desc = args.desc;
273 request->query.offset = args.offset;
274 request->query.limit = args.limit;
275
276 RCLCPP_INFO(node->get_logger(),
277 "Querying history: state=%s, offset=%u, limit=%u, desc=%s",
278 StateName(args.state).c_str(), args.offset, args.limit,
279 args.desc ? "true" : "false");
280
281 auto future = client->async_send_request(request);
282 if (rclcpp::spin_until_future_complete(node, future, 10s) !=
283 rclcpp::FutureReturnCode::SUCCESS) {
284 RCLCPP_ERROR(node->get_logger(), "Service call timeout");
285 rclcpp::shutdown();
286 return 1;
287 }
288
289 PrintRecords(*future.get());
290 rclcpp::shutdown();
291 return 0;
292}
293
294void PrintUsage(const char *prog) {
295 std::cout << "Usage:\n"
296 << " " << prog << " echo\n"
297 << " " << prog
298 << " history [--state N] [--limit N] [--offset N] [--desc]\n"
299 << " [--start-ns N] [--end-ns N]\n"
300 << " [--diag-codes 0xAAAA 0xBBBB ...]\n"
301 << " [--alert-codes \"code1\" \"code2\" ...]\n";
302}
303
304} // namespace
305
306int main(int argc, char **argv) {
307 if (argc < 2) {
308 PrintUsage(argv[0]);
309 return 1;
310 }
311
312 std::string mode = argv[1];
313 if (mode == "echo")
314 return RunEcho(argc, argv);
315 if (mode == "history")
316 return RunHistory(argc, argv);
317
318 PrintUsage(argv[0]);
319 return 1;
320}
使用说明
实时订阅 (echo):
# 直接订阅三个话题,实时打印诊断码、告警码、后处理信号
ros2 run examples hds_diagnostics echo
话题 |
数据类型 |
|---|---|
|
|
|
|
|
|
查询历史事件 (history),所有过滤条件均对应 HdsEventQuery 字段,使用示例:
# 查最近 20 条 (默认 limit=20,正序)
ros2 run examples hds_diagnostics history
# 按时间范围查询 (对应 start_time / end_time)
ros2 run examples hds_diagnostics history \
--start-ns 1716000000000000000 \
--end-ns 1716100000000000000
# 只看活跃中的告警 (对应 state = STATE_ACTIVE)
ros2 run examples hds_diagnostics history --state 1
# 按诊断码过滤 (对应 diag_codes,支持十六进制)
ros2 run examples hds_diagnostics history --diag-codes 0x6200000001 0x6200000002
# 按告警码过滤 (对应 alert_codes)
ros2 run examples hds_diagnostics history --alert-codes MOTOR_OVERHEAT COMM_LOST
# 分页 + 倒序 (对应 limit / offset / order_desc,最新的先显示)
ros2 run examples hds_diagnostics history --limit 50 --offset 0 --desc
# 组合使用:只看已恢复的,每次取 10 条,最新的先显示
ros2 run examples hds_diagnostics history --state 2 --limit 10 --desc
返回字段对应 HdsEventRecord。
echo 模式输出示例
[INFO] [hds_diagnostics_echo]: Subscribed: diag_code_list / alert_code_list / post_proc/signal
[INFO] [hds_diagnostics_echo]: [Diagnostics] count=1
diag_code=0x6200000007 type=APPEAR module_id=98 ts=1770125762202825297 info="获取不到机器人定位"
[INFO] [hds_diagnostics_echo]: [Alerts] count=1
code="LOCALIZATION_LOST" ts=1770125762202825297 first_ts=1770125762202825297
[INFO] [hds_diagnostics_echo]: [PostProc] count=1
diag_code=0x6200000007 ts=1770125762202825297 robot=1 motion=2 sound=1 led=3 facial=0 funcdisable=0x0000000000000004
history 模式输出示例
[INFO] [hds_history_client]: Querying history: state=ALL, offset=0, limit=20, desc=true
total_count = 5, returned = 5, has_more = false
--------------------------------------------------------------------------------
id=42 diag=0x6200000007 alert="LOCALIZATION_LOST"
first_ts=1770125762202825297 last_ts=1770125765310145000
recovered=YES recover_ts=1770125770000000000 duration=7.797s
--------------------------------------------------------------------------------
接口参考
话题:
/aima/hds/diag_code_list、/aima/hds/alert_code_list、/aima/hds/post_proc/signal服务:
/aimdk_5Fmsgs/srv/GetHdsEventHistory消息:
aimdk_msgs/msg/DiagnosticInfoArray、aimdk_msgs/msg/AlertCodeArray、aimdk_msgs/msg/PostProcSigArray