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库来控制机器人的关节运动。示例实现了以下功能:

  1. 机器人关节模型定义

  2. 基于Ruckig的轨迹插补

  3. 多关节协同控制

  4. 实时位置、速度和加速度控制

依赖项

  • ROS2

  • Ruckig库

  • aimdk_msgs包

构建说明

  1. 将代码放入ROS2工作空间的src目录

  2. 在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
)
  1. 在package.xml中添加依赖:

<depend>rclcpp</depend>
<depend>aimdk_msgs</depend>
<depend>ruckig</depend>

示例功能说明

  1. 创建了四个控制器节点,分别控制:

    • 腿部x2(12个关节)

    • 腰部x1(3个关节)

    • 手臂x2(14个关节)

    • 头部x1(2个关节)

  2. 演示功能:

    • 每10秒让指定关节在正负0.5弧度之间摆动

    • 使用Ruckig库实现平滑的运动轨迹

    • 实时发布关节控制命令

自定义使用

  1. 添加新的控制逻辑:

    • 修改SetTargetPosition函数

    • 添加新的控制回调函数

  2. 调整控制频率:

    • 修改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}

使用说明:

  1. 订阅RGB图像数据:

    ros2 run examples echo_camera_rgbd --ros-args -p topic_type:=rgb_image
    
  2. 订阅相机内参:

    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
    
  3. 录制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}

使用说明:

  1. 订阅左相机RGB图像:

    ros2 run examples echo_camera_stereo --ros-args -p topic_type:=left_rgb_image
    
  2. 订阅右相机RGB图像:

    ros2 run examples echo_camera_stereo --ros-args -p topic_type:=right_rgb_image
    
  3. 订阅左相机内参:

    ros2 run examples echo_camera_stereo --ros-args -p topic_type:=left_camera_info
    
  4. 录制左相机视频:

    # 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}

使用说明:

  1. 订阅RGB图像数据:

    ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image
    
  2. 订阅压缩图像数据:

    ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image_compressed
    
  3. 订阅相机内参:

    ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=camera_info
    
  4. 录制视频:

    # 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
    
  5. 录制视频并对把手遮挡区域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
    
  6. 禁用自动旋转补偿(获取原始图像):

    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}

使用说明:

  1. 运行程序:

    # 构建
    colcon build --packages-select examples
    
    # 运行麦克风接收程序, 配合唤醒词触发VAD
    ros2 run examples mic_receiver
    

    按 Ctrl+C 安全退出程序,程序会自动处理音频流的开始和结束。

  2. 查找录制的PCM文件:

    • 运行节点后会自动创建audio_recordings目录

    • 音频文件按stream_id分类存储(暂不区分, 一律使用stream_1):

      • stream_1/: 内置麦克风音频

      • stream_2/: 外置麦克风音频

    • 文件命名格式:{stream_name}_{timestamp}.pcm

      • internal_mic_20250909_133649_738.pcm (内置麦克风)

      • external_mic_20250909_133650_123.pcm (外置麦克风)

  3. 播放保存的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格式导入并播放。

  4. 转换为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}

使用说明:

  1. 运行程序:

    # 构建
    colcon build --packages-select examples
    
    # 运行麦克风接收程序
    ros2 run examples mic_raw_receiver
    
  2. 查找录制的PCM文件:

    • 运行节点后会自动创建audio_recordings目录

    • 音频文件按录制程序本次启动时间归集存储至如下格式的目录:

      • 20250909_133650_123

    • 文件命名格式:channel_{index}_{type}.pcm,如

      • channel_0_mic.pcm(麦克风)

      • channel_4_ref.pcm(回采)

  3. 播放保存的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.30 导航

该示例提供了交互式终端菜单,支持导航任务的开始、暂停、恢复和停止操作。

  1#include <chrono>
  2#include <cinttypes>
  3#include <cstring>
  4#include <iostream>
  5#include <string>
  6#include <thread>
  7
  8#include <aimdk_msgs/msg/message_header.hpp>
  9#include <aimdk_msgs/msg/pnc_task_request.hpp>
 10#include <geometry_msgs/msg/pose_stamped.hpp>
 11#include <rclcpp/rclcpp.hpp>
 12
 13using namespace std::chrono_literals;
 14
 15// task_request enum
 16constexpr uint8_t TASK_REQUEST_START = 1;
 17constexpr uint8_t TASK_REQUEST_STOP = 2;
 18constexpr uint8_t TASK_REQUEST_PAUSE = 3;
 19constexpr uint8_t TASK_REQUEST_RESUME = 4;
 20
 21class NavigationNode : public rclcpp::Node {
 22public:
 23  NavigationNode() : Node("navigation_example") {
 24    publisher_ = this->create_publisher<aimdk_msgs::msg::PncTaskRequest>(
 25        "/aima/te/pnc_task_request", 10);
 26
 27    auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
 28                      std::chrono::system_clock::now().time_since_epoch())
 29                      .count();
 30    task_id_ = static_cast<uint64_t>(now_ms);
 31    map_id_ = 0;
 32
 33    RCLCPP_INFO(this->get_logger(),
 34                "Navigation example node started, task_id=%" PRIu64, task_id_);
 35  }
 36
 37  void send_start(uint64_t map_id, double x, double y, double z, double ox,
 38                  double oy, double oz, double ow, double radius,
 39                  uint8_t pnc_mode, double max_speed) {
 40    map_id_ = map_id;
 41    auto msg = aimdk_msgs::msg::PncTaskRequest();
 42    msg.header = make_header();
 43    msg.task_type = 2;
 44    msg.task_request = TASK_REQUEST_START;
 45    msg.pnc_mode = pnc_mode;
 46    msg.max_forward_speed = max_speed;
 47    msg.task_id = task_id_;
 48    msg.map_id = map_id;
 49    msg.target_pose_radius = radius;
 50    std::memset(msg.reserve_info.data(), 0, msg.reserve_info.size());
 51
 52    auto &target = msg.target_pose;
 53    target.header.stamp = this->get_clock()->now();
 54    target.header.frame_id = "map";
 55    target.pose.position.x = x;
 56    target.pose.position.y = y;
 57    target.pose.position.z = z;
 58    target.pose.orientation.x = ox;
 59    target.pose.orientation.y = oy;
 60    target.pose.orientation.z = oz;
 61    target.pose.orientation.w = ow;
 62
 63    RCLCPP_INFO(this->get_logger(),
 64                "[Start] task_id=%" PRIu64 ", map_id=%" PRIu64 ", "
 65                "target=(%.2f, %.2f, %.2f), radius=%.2f",
 66                task_id_, map_id, x, y, z, radius);
 67    start_publishing(msg);
 68  }
 69
 70  void send_pause() {
 71    auto msg = make_simple_request(TASK_REQUEST_PAUSE, 0);
 72    RCLCPP_INFO(this->get_logger(), "[Pause] task_id=%" PRIu64, task_id_);
 73    start_publishing(msg);
 74  }
 75
 76  void send_resume() {
 77    auto msg = make_simple_request(TASK_REQUEST_RESUME, map_id_);
 78    RCLCPP_INFO(this->get_logger(),
 79                "[Resume] task_id=%" PRIu64 ", map_id=%" PRIu64, task_id_,
 80                map_id_);
 81    start_publishing(msg);
 82  }
 83
 84  void send_stop() {
 85    auto msg = make_simple_request(TASK_REQUEST_STOP, map_id_);
 86    RCLCPP_INFO(this->get_logger(),
 87                "[Stop] task_id=%" PRIu64 ", map_id=%" PRIu64, task_id_,
 88                map_id_);
 89    start_publishing(msg);
 90  }
 91
 92  void stop_publishing() {
 93    if (timer_) {
 94      timer_->cancel();
 95      timer_.reset();
 96      RCLCPP_INFO(this->get_logger(), "Stopped continuous publishing");
 97    }
 98  }
 99
100  uint64_t task_id() const { return task_id_; }
101
102private:
103  void start_publishing(const aimdk_msgs::msg::PncTaskRequest &msg) {
104    current_msg_ = msg;
105    if (timer_) {
106      timer_->cancel();
107    }
108    publisher_->publish(current_msg_);
109    timer_ = this->create_wall_timer(1s, [this]() {
110      current_msg_.header.stamp = this->get_clock()->now();
111      current_msg_.header.meas_stamp = this->get_clock()->now();
112      current_msg_.target_pose.header.stamp = this->get_clock()->now();
113      publisher_->publish(current_msg_);
114    });
115    RCLCPP_INFO(this->get_logger(), "Continuous publishing at 1Hz");
116  }
117
118  aimdk_msgs::msg::MessageHeader make_header() {
119    auto header = aimdk_msgs::msg::MessageHeader();
120    auto now = this->get_clock()->now();
121    header.stamp = now;
122    header.meas_stamp = now;
123    header.frame_id = "map";
124    header.sequence = 0;
125    return header;
126  }
127
128  aimdk_msgs::msg::PncTaskRequest make_simple_request(uint8_t task_request,
129                                                      uint64_t map_id) {
130    auto msg = aimdk_msgs::msg::PncTaskRequest();
131    msg.header = make_header();
132    msg.task_type = 2;
133    msg.task_request = task_request;
134    msg.pnc_mode = 0;
135    msg.max_forward_speed = 0.5;
136    msg.task_id = task_id_;
137    msg.map_id = map_id;
138    msg.target_pose_radius = 0.0;
139    std::memset(msg.reserve_info.data(), 0, msg.reserve_info.size());
140    msg.target_pose.pose.orientation.w = 1.0;
141    return msg;
142  }
143
144  rclcpp::Publisher<aimdk_msgs::msg::PncTaskRequest>::SharedPtr publisher_;
145  rclcpp::TimerBase::SharedPtr timer_;
146  aimdk_msgs::msg::PncTaskRequest current_msg_;
147  uint64_t task_id_;
148  uint64_t map_id_;
149};
150
151template <typename T> T input_value(const std::string &prompt, T default_val) {
152  std::cout << prompt << " [" << default_val << "]: ";
153  std::string line;
154  std::getline(std::cin, line);
155  if (line.empty()) {
156    return default_val;
157  }
158  if constexpr (std::is_same_v<T, uint64_t>) {
159    return std::stoull(line);
160  } else if constexpr (std::is_same_v<T, uint8_t>) {
161    return static_cast<uint8_t>(std::stoi(line));
162  } else if constexpr (std::is_integral_v<T>) {
163    return static_cast<T>(std::stoll(line));
164  } else {
165    return static_cast<T>(std::stod(line));
166  }
167}
168
169void print_menu(uint64_t task_id) {
170  std::cout << "\n========================================\n"
171            << "  Navigation Control  (task_id=" << task_id << ")\n"
172            << "========================================\n"
173            << "  1. Start navigation\n"
174            << "  2. Pause navigation\n"
175            << "  3. Resume navigation\n"
176            << "  4. Stop navigation\n"
177            << "  q. Quit\n"
178            << "========================================\n";
179}
180
181int main(int argc, char *argv[]) {
182  rclcpp::init(argc, argv);
183  auto node = std::make_shared<NavigationNode>();
184
185  std::thread spin_thread([node]() { rclcpp::spin(node); });
186
187  RCLCPP_INFO(node->get_logger(),
188              "Waiting for node communication ready (5s)...");
189  std::this_thread::sleep_for(5s);
190
191  std::string choice;
192  while (rclcpp::ok()) {
193    print_menu(node->task_id());
194    std::cout << "Select an option: ";
195    std::getline(std::cin, choice);
196
197    if (choice == "1") {
198      std::cout << "\n--- Start Navigation Parameters ---\n";
199      auto map_id = input_value<uint64_t>("Map ID (map_id)", 1773113429735ULL);
200      auto x = input_value<double>("Target x", 1.0);
201      auto y = input_value<double>("Target y", 2.0);
202      auto z = input_value<double>("Target z", 0.0);
203      auto ox = input_value<double>("Orientation x", 0.0);
204      auto oy = input_value<double>("Orientation y", 0.0);
205      auto oz = input_value<double>("Orientation z", 0.0);
206      auto ow = input_value<double>("Orientation w", 1.0);
207      auto radius = input_value<double>("Target pose radius", 0.5);
208      auto pnc_mode = input_value<uint8_t>("pnc_mode", 0);
209      auto max_speed = input_value<double>("Max forward speed", 0.5);
210      node->send_start(map_id, x, y, z, ox, oy, oz, ow, radius, pnc_mode,
211                       max_speed);
212    } else if (choice == "2") {
213      node->send_pause();
214    } else if (choice == "3") {
215      node->send_resume();
216    } else if (choice == "4") {
217      node->send_stop();
218    } else if (choice == "q" || choice == "Q") {
219      node->stop_publishing();
220      std::cout << "Exiting navigation control.\n";
221      break;
222    } else {
223      std::cout << "Invalid input, please try again.\n";
224    }
225  }
226
227  rclcpp::shutdown();
228  spin_thread.join();
229  return 0;
230}

使用说明:

ros2 run examples navigation

输出示例:

[INFO] [navigation_example]: Navigation example node started, task_id=1709123456789
[INFO] [navigation_example]: Waiting for node communication ready (5s)...

========================================
  Navigation Control  (task_id=1709123456789)
========================================
  1. Start navigation
  2. Pause navigation
  3. Resume navigation
  4. Stop navigation
  q. Quit
========================================
Select an option: 1

--- Start Navigation Parameters ---
Map ID (map_id) [1773113429735]:
Target x [1]:
Target y [2]:
...
[INFO] [navigation_example]: [Start] task_id=1709123456789, map_id=1773113429735, target=(1.00, 2.00, 0.00), radius=0.50

开始导航时需输入地图ID、目标位姿、到点精度等参数,每项都有默认值,直接回车即可使用默认值。

参数获取方式:map_id 的获取见 地图标识与坐标的获取;target_x / target_y 为目标点米制坐标(map 坐标系),与重定位用的像素坐标不同,根据实际任务直接指定

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

话题

数据类型

/aima/hds/diag_code_list

aimdk_msgs/msg/DiagnosticInfoArray

/aima/hds/alert_code_list

aimdk_msgs/msg/AlertCodeArray

/aima/hds/post_proc/signal

aimdk_msgs/msg/PostProcSigArray

查询历史事件 (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