6.2 C++接口使用示例

本章节将带您实现索引所示若干功能

构建 & 运行说明

  • 进入到解压后的 sdk 顶层目录,执行以下命令

    source /opt/ros/humble/setup.bash
    colcon build
    source install/setup.bash
    ros2 run examples '对应功能名称如: get_mc_action'
    

📝 代码说明

完整的代码实现包含完整的错误处理、信号处理、超时处理等机制,确保程序的健壮性。请您在 examples 目录下查看/修改

小心

ROS的服务(service)机制在跨板通信时存在一些待优化问题, 二次开发时请参考例程添加异常处理、快速重试等保护机制

6.2.1 进入开发者模式

通过调用'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) {
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    // 2. System status equals 1 (IN_READY)
263    bool state_match = (current_state_lower == target_state_lower);
264    bool status_match = (response->curr_status.value ==
265                         aimdk_msgs::msg::SystemStatus::IN_READY);
266
267    if (state_match && status_match) {
268      return true;
269    }
270
271    // Log detailed information if not complete
272    if (!state_match) {
273      RCLCPP_INFO(this->get_logger(),
274                  "State mismatch: current='%s', expected='%s'",
275                  response->cur_state.c_str(), target_state_.c_str());
276    }
277    if (!status_match) {
278      RCLCPP_INFO(this->get_logger(),
279                  "Status mismatch: current= %u, expected=1 (IN_READY)",
280                  response->curr_status.value);
281    }
282    if (state_match && !state_match) {
283      RCLCPP_ERROR(this->get_logger(), "State Migrate Fail");
284      exit(0);
285    }
286
287    return false;
288  }
289
290  // Target state to migrate to
291  std::string target_state_;
292
293  // Service clients
294  rclcpp::Client<aimdk_msgs::srv::MigrateSystemState>::SharedPtr
295      migrate_client_;
296  rclcpp::Client<aimdk_msgs::srv::GetSystemState>::SharedPtr get_state_client_;
297
298  // Timers
299  rclcpp::TimerBase::SharedPtr check_timer_;
300  rclcpp::TimerBase::SharedPtr monitor_timer_;
301
302  // Retry counters and timeout timers for migration
303  int migrate_retry_count_ = 0;
304  const int max_migrate_retries_ = 5;
305  uint64_t migrate_request_id_ = 0;
306  bool migrate_pending_ = false;
307  rclcpp::TimerBase::SharedPtr migrate_timeout_timer_;
308
309  // Retry counters and timeout timers for get state
310  int get_state_retry_count_ = 0;
311  const int max_get_state_retries_ = 3;
312  uint64_t get_state_request_id_ = 0;
313  bool get_state_pending_ = false;
314  rclcpp::TimerBase::SharedPtr get_state_timeout_timer_;
315};
316
317/**
318 * @brief Print usage information with detailed mode descriptions
319 */
320void print_usage(const char *program_name) {
321  std::cout << "Usage: " << program_name << " <target_state>" << std::endl;
322  std::cout << std::endl;
323  std::cout << "Available target states:" << std::endl;
324  std::cout << "  " << program_name << " Ready" << std::endl;
325  std::cout << "  " << program_name << " Develop_Nav" << std::endl;
326  std::cout << "  " << program_name << " Develop_Audio_Linux" << std::endl;
327  std::cout << "  " << program_name << " Develop_Audio_ROS" << std::endl;
328  std::cout << "  " << program_name << " Develop_MC" << std::endl;
329  std::cout << std::endl;
330
331  std::cout << "Mode Descriptions:" << std::endl;
332  std::cout << "==================" << std::endl;
333
334  // Ready mode description
335  std::cout << "1. Ready - System Default Mode" << std::endl;
336  std::cout
337      << "   • Description: System enters this mode by default after startup"
338      << std::endl;
339  std::cout << "   • Purpose: Normal operation mode, ready for general tasks"
340            << std::endl;
341  std::cout
342      << "   • Use case: Return to normal operation after development work"
343      << std::endl;
344  std::cout << std::endl;
345
346  // Develop_Nav mode description
347  std::cout << "2. Develop_Nav - Navigation Development Mode" << std::endl;
348  std::cout
349      << "   • Description: Special mode for navigation system development"
350      << std::endl;
351  std::cout << "   • Purpose: Enables navigation-related debugging and testing"
352            << std::endl;
353  std::cout << "   • Use case: When developing or testing navigation "
354               "algorithms, SLAM, path planning"
355            << std::endl;
356  std::cout << "   • Features: May provide access to raw sensor data, "
357               "navigation debug topics"
358            << std::endl;
359  std::cout << std::endl;
360
361  // Develop_Audio_Linux mode description
362  std::cout << "3. Develop_Audio_Linux - System-Level Audio Development Mode"
363            << std::endl;
364  std::cout << "   • Description: Mode for low-level audio system development"
365            << std::endl;
366  std::cout << "   • Purpose: Access to system audio streams at Linux level"
367            << std::endl;
368  std::cout << "   • Use case: When developing audio drivers, audio processing "
369               "at system level"
370            << std::endl;
371  std::cout << "   • Features: Direct access to audio hardware, "
372               "ALSA/PulseAudio interfaces"
373            << std::endl;
374  std::cout << std::endl;
375
376  // Develop_Audio_ROS mode description
377  std::cout << "4. Develop_Audio_ROS - ROS Audio Development Mode" << std::endl;
378  std::cout
379      << "   • Description: Mode for ROS-based audio application development"
380      << std::endl;
381  std::cout << "   • Purpose: Access to audio data through ROS topics"
382            << std::endl;
383  std::cout << "   • Use case: When developing ROS nodes that process audio, "
384               "speech recognition"
385            << std::endl;
386  std::cout << "   • Features: Audio data published as ROS topics, ROS message "
387               "interfaces"
388            << std::endl;
389  std::cout << std::endl;
390
391  // Develop_MC mode description
392  std::cout << "5. Develop_MC - Motion Control Development Mode" << std::endl;
393  std::cout << "   • Description: Mode for motion control system development"
394            << std::endl;
395  std::cout
396      << "   • Purpose: Enables direct control and testing of motion systems"
397      << std::endl;
398  std::cout << "   • Use case: When developing motor control algorithms, "
399               "testing motion hardware"
400            << std::endl;
401  std::cout << "   • Features: Low-level motor control access, motion system "
402               "debugging tools"
403            << std::endl;
404  std::cout << std::endl;
405
406  std::cout << "Program Workflow:" << std::endl;
407  std::cout << "=================" << std::endl;
408  std::cout
409      << "1. Calls MigrateSystemState service with the specified target state"
410      << std::endl;
411  std::cout << "2. Continuously monitors GetSystemState service every second"
412            << std::endl;
413  std::cout << "3. Exits when both conditions are met:" << std::endl;
414  std::cout << "   • Current State equals target state (case-insensitive)"
415            << std::endl;
416  std::cout << "   • System Status equals 1 (IN_READY)" << std::endl;
417  std::cout << std::endl;
418  std::cout << "Note: The program will automatically exit when migration is "
419               "complete or on error."
420            << std::endl;
421}
422
423/**
424 * @brief Validate if the target state is a known development mode
425 * @param state The state to validate
426 * @return true if state is valid, false otherwise
427 */
428bool is_valid_state(const std::string &state) {
429  // List of known valid states
430  const std::vector<std::string> valid_states = {
431      "Ready", "Develop_Nav", "Develop_Audio_Linux", "Develop_Audio_ROS",
432      "Develop_MC"};
433
434  // Convert input to lowercase for case-insensitive comparison
435  std::string state_lower = state;
436  std::transform(state_lower.begin(), state_lower.end(), state_lower.begin(),
437                 ::tolower);
438
439  // Check if state is in the list of valid states
440  for (const auto &valid_state : valid_states) {
441    std::string valid_state_lower = valid_state;
442    std::transform(valid_state_lower.begin(), valid_state_lower.end(),
443                   valid_state_lower.begin(), ::tolower);
444
445    if (state_lower == valid_state_lower) {
446      return true;
447    }
448  }
449
450  return false;
451}
452
453/**
454 * @brief Main function
455 * @param argc Argument count
456 * @param argv Argument vector
457 * @return Exit code
458 */
459int main(int argc, char **argv) {
460  // Check command line arguments
461  if (argc != 2) {
462    print_usage(argv[0]);
463    return 1;
464  }
465
466  // Get target state from command line argument
467  std::string target_state = argv[1];
468
469  // Validate target state
470  if (target_state.empty()) {
471    std::cerr << "Error: Target state cannot be empty" << std::endl;
472    print_usage(argv[0]);
473    return 1;
474  }
475
476  // Optional: Validate against known states
477  if (!is_valid_state(target_state)) {
478    std::cout << "Warning: '" << target_state
479              << "' is not in the list of known states." << std::endl;
480    std::cout << "The program will still attempt to migrate to this state."
481              << std::endl;
482    std::cout << "Continue? (y/n): ";
483
484    std::string response;
485    std::getline(std::cin, response);
486
487    if (response != "y" && response != "Y") {
488      std::cout << "Operation cancelled." << std::endl;
489      return 0;
490    }
491  }
492
493  // Initialize ROS2
494  rclcpp::init(argc, argv);
495
496  // Create migrator node with target state
497  auto node = std::make_shared<SystemStateMigrator>(target_state);
498
499  // Create single-threaded executor
500  auto executor = std::make_shared<rclcpp::executors::SingleThreadedExecutor>();
501
502  // Add node to executor
503  executor->add_node(node);
504
505  // Run executor (blocking call)
506  executor->spin();
507
508  // Cleanup
509  executor->remove_node(node);
510  rclcpp::shutdown();
511
512  return 0;
513}

使用说明

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...
[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] [1764066631.021247791] [get_mc_action_client]: Current robot mode:
[INFO] [1764066631.021832667] [get_mc_action_client]: Mode name: PASSIVE_DEFAULT
[INFO] [1764066631.022396136] [get_mc_action_client]: Mode status: 100

接口参考

  • 服务:/aimdk_5Fmsgs/srv/GetMcAction

  • 消息:aimdk_msgs/srv/GetMcAction

6.2.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 <memory>
 11#include <signal.h>
 12#include <unordered_map>
 13#include <vector>
 14
 15// Global variable used for signal handling
 16std::shared_ptr<rclcpp::Node> g_node = nullptr;
 17
 18// Signal handler function
 19void signal_handler(int signal) {
 20  if (g_node) {
 21    RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
 22                signal);
 23    g_node.reset();
 24  }
 25  rclcpp::shutdown();
 26  exit(signal);
 27}
 28
 29class SetMcActionClient : public rclcpp::Node {
 30public:
 31  SetMcActionClient() : Node("set_mc_action_client") {
 32
 33    client_ = this->create_client<aimdk_msgs::srv::SetMcAction>(
 34        "/aimdk_5Fmsgs/srv/SetMcAction");
 35    RCLCPP_INFO(this->get_logger(), "✅ SetMcAction client node created.");
 36
 37    // Wait for the service to become available
 38    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 39      RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
 40    }
 41    RCLCPP_INFO(this->get_logger(),
 42                "🟢 Service available, ready to send request.");
 43  }
 44
 45  bool send_request(std::string &action_name) {
 46    try {
 47      auto request = std::make_shared<aimdk_msgs::srv::SetMcAction::Request>();
 48      request->header = aimdk_msgs::msg::RequestHeader();
 49
 50      request->source = "node";
 51
 52      // Set robot mode
 53      aimdk_msgs::msg::McActionCommand command;
 54      command.action_desc = action_name;
 55      request->command = command;
 56
 57      RCLCPP_INFO(this->get_logger(), "📨 Sending request to set robot mode: %s",
 58                  action_name.c_str());
 59
 60      // Set Service Call Timeout
 61      const std::chrono::milliseconds timeout(250);
 62      for (int i = 0; i < 8; i++) {
 63        request->header.stamp = this->now();
 64        auto future = client_->async_send_request(request);
 65        auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
 66                                                          future, timeout);
 67        if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
 68          // retry as remote peer is NOT handled well by ROS
 69          RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
 70          continue;
 71        }
 72        // future.done
 73        auto response = future.get();
 74        if (response->response.status.value ==
 75            aimdk_msgs::msg::CommonState::SUCCESS) {
 76          RCLCPP_INFO(this->get_logger(), "✅ Robot mode set successfully.");
 77          return true;
 78        } else {
 79          RCLCPP_ERROR(this->get_logger(), "❌ Failed to set robot mode: %s",
 80                       response->response.message.c_str());
 81          return false;
 82        }
 83      }
 84      RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
 85      return false;
 86    } catch (const std::exception &e) {
 87      RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
 88      return false;
 89    }
 90  }
 91
 92private:
 93  rclcpp::Client<aimdk_msgs::srv::SetMcAction>::SharedPtr client_;
 94};
 95
 96static std::unordered_map<std::string, std::vector<std::string>> g_action_info =
 97    {
 98        {"PASSIVE_DEFAULT", {"PD", "joints with zero torque"}},
 99        {"DAMPING_DEFAULT", {"DD", "joints in damping mode"}},
100        {"JOINT_DEFAULT", {"JD", "Position Control Stand (joints locked)"}},
101        {"STAND_DEFAULT", {"SD", "Stable Stand (auto-balance)"}},
102        {"LOCOMOTION_DEFAULT", {"LD", "locomotion mode (walk or run)"}},
103};
104
105int main(int argc, char *argv[]) {
106  try {
107    rclcpp::init(argc, argv);
108
109    // Set up signal handlers
110    signal(SIGINT, signal_handler);
111    signal(SIGTERM, signal_handler);
112
113    // Create node
114    g_node = std::make_shared<SetMcActionClient>();
115    auto client = std::dynamic_pointer_cast<SetMcActionClient>(g_node);
116
117    if (client) {
118      std::unordered_map<std::string, std::string> choices;
119      std::string motion;
120
121      // Prefer command-line argument; otherwise prompt user
122      if (argc > 1) {
123        motion = argv[1];
124        RCLCPP_INFO(g_node->get_logger(),
125                    "Using abbr of motion mode from cmdline: %s", argv[1]);
126      } else {
127        std::cout << std::left << std::setw(4) << "abbr"
128                  << " - " << std::setw(20) << "robot mode"
129                  << " : "
130                  << "description" << std::endl;
131        for (auto &it : g_action_info) {
132          std::cout << std::left << std::setw(4) << it.second[0] << " - "
133                    << std::setw(20) << it.first << " : " << it.second[1]
134                    << std::endl;
135        }
136        std::cout << "Enter abbr of motion mode:";
137        std::cin >> motion;
138      }
139      for (auto &it : g_action_info) {
140        choices[it.second[0]] = it.first;
141      }
142
143      auto m = choices.find(motion);
144      if (m != choices.end()) {
145        auto &action_name = m->second;
146        client->send_request(action_name);
147      } else {
148        RCLCPP_ERROR(g_node->get_logger(), "Invalid abbr of robot mode: %s",
149                     motion.c_str());
150      }
151    }
152
153    // Clean up resources
154    g_node.reset();
155    rclcpp::shutdown();
156
157    return 0;
158  } catch (const std::exception &e) {
159    RCLCPP_ERROR(rclcpp::get_logger("main"),
160                 "Program exited with exception: %s", e.what());
161    return 1;
162  }
163}

使用说明

# 使用命令行参数设置模式(推荐)
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_preset_motion.hpp"
  6#include "aimdk_msgs/msg/request_header.hpp"
  7#include "aimdk_msgs/srv/set_mc_preset_motion.hpp"
  8#include "rclcpp/rclcpp.hpp"
  9#include <chrono>
 10#include <memory>
 11#include <signal.h>
 12
 13std::shared_ptr<rclcpp::Node> g_node = nullptr;
 14
 15void signal_handler(int signal) {
 16  if (g_node) {
 17    RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
 18                signal);
 19    g_node.reset();
 20  }
 21  rclcpp::shutdown();
 22  exit(signal);
 23}
 24
 25class PresetMotionClient : public rclcpp::Node {
 26public:
 27  PresetMotionClient() : Node("preset_motion_client") {
 28    const std::chrono::seconds timeout(8);
 29
 30    client_ = this->create_client<aimdk_msgs::srv::SetMcPresetMotion>(
 31        "/aimdk_5Fmsgs/srv/SetMcPresetMotion");
 32
 33    RCLCPP_INFO(this->get_logger(), "✅ SetMcPresetMotion client node created.");
 34
 35    // Wait for the service to become available
 36    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 37      RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
 38    }
 39    RCLCPP_INFO(this->get_logger(),
 40                "🟢 Service available, ready to send request.");
 41  }
 42
 43  bool send_request(int area_id, int motion_id) {
 44    try {
 45      auto request =
 46          std::make_shared<aimdk_msgs::srv::SetMcPresetMotion::Request>();
 47      request->header = aimdk_msgs::msg::RequestHeader();
 48
 49      aimdk_msgs::msg::McPresetMotion motion;
 50      aimdk_msgs::msg::McControlArea area;
 51
 52      motion.value = motion_id; // Preset motion ID
 53      area.value = area_id;     // Control area ID
 54      request->motion = motion;
 55      request->area = area;
 56      request->interrupt = false; // Not interrupt current motion
 57
 58      RCLCPP_INFO(this->get_logger(),
 59                  "📨 Sending request to set preset motion: motion=%d, area=%d",
 60                  motion_id, area_id);
 61
 62      const std::chrono::milliseconds timeout(250);
 63      for (int i = 0; i < 8; i++) {
 64        request->header.stamp = this->now();
 65        auto future = client_->async_send_request(request);
 66        auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
 67                                                          future, timeout);
 68        if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
 69          // retry as remote peer is NOT handled well by ROS
 70          RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
 71          continue;
 72        }
 73        // future.done
 74        auto response = future.get();
 75        if (response->response.header.code == 0) {
 76          RCLCPP_INFO(this->get_logger(),
 77                      "✅ Preset motion set successfully: %lu",
 78                      response->response.task_id);
 79          return true;
 80        } else if (response->response.state.value ==
 81                   aimdk_msgs::msg::CommonState::RUNNING) {
 82          RCLCPP_INFO(this->get_logger(), "⏳ Preset motion executing: %lu",
 83                      response->response.task_id);
 84          return true;
 85        } else {
 86          RCLCPP_WARN(this->get_logger(), "❌ Failed to set preset motion: %lu",
 87                      response->response.task_id);
 88          return false;
 89        }
 90      }
 91      RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
 92      return false;
 93    } catch (const std::exception &e) {
 94      RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
 95      return false;
 96    }
 97  }
 98
 99private:
100  rclcpp::Client<aimdk_msgs::srv::SetMcPresetMotion>::SharedPtr client_;
101};
102
103int main(int argc, char *argv[]) {
104  try {
105    rclcpp::init(argc, argv);
106    signal(SIGINT, signal_handler);
107    signal(SIGTERM, signal_handler);
108
109    g_node = std::make_shared<PresetMotionClient>();
110    // Cast g_node (std::shared_ptr<rclcpp::Node>) to a derived
111    // PresetMotionClient pointer (std::shared_ptr<PresetMotionClient>)
112    auto client = std::dynamic_pointer_cast<PresetMotionClient>(g_node);
113
114    int area = 1;
115    int motion = 1003;
116    std::cout << "Enter arm area ID (1-left, 2-right): ";
117    std::cin >> area;
118    std::cout
119        << "Enter preset motion ID (1001-raise, 1002-wave, 1003-handshake, "
120           "1004-airkiss): ";
121    std::cin >> motion;
122    if (client) {
123      client->send_request(area, motion);
124    }
125
126    // Clean up resources
127    g_node.reset();
128    rclcpp::shutdown();
129
130    return 0;
131  } catch (const std::exception &e) {
132    RCLCPP_ERROR(rclcpp::get_logger("main"),
133                 "Program exited with exception: %s", e.what());
134    return 1;
135  }
136}

6.2.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 <vector>
 8
 9class HandControl : public rclcpp::Node {
10public:
11  HandControl()
12      : Node("hand_control"), position_pairs_({
13                                  {1.0, 1.0},
14                                  {0.0, 0.0},
15                                  {0.5, 0.5},
16                                  {0.2, 0.8},
17                                  {0.7, 0.3},
18                              }),
19        current_index_(0) {
20    publisher_ = this->create_publisher<aimdk_msgs::msg::HandCommandArray>(
21        "/aima/hal/joint/hand/command", 10);
22
23    timer_ = this->create_wall_timer(
24        std::chrono::milliseconds(20), // 50Hz
25        std::bind(&HandControl::publish_hand_commands, this));
26
27    last_switch_time_ = now();
28    RCLCPP_INFO(this->get_logger(), "The hand control node has been started!");
29  }
30
31  void publish_hand_commands() {
32    // 1. Determine if it's time to switch parameters.
33    auto now_time = this->now();
34    if ((now_time - last_switch_time_).seconds() >= 2.0) {
35      current_index_ = (current_index_ + 1) % position_pairs_.size();
36      last_switch_time_ = now_time;
37      RCLCPP_INFO(this->get_logger(),
38                  "Switched to the next parameter group, index=%zu (left=%.2f, "
39                  "right=%.2f)",
40                  current_index_, position_pairs_[current_index_].first,
41                  position_pairs_[current_index_].second);
42    }
43
44    auto msg = std::make_unique<aimdk_msgs::msg::HandCommandArray>();
45    msg->header = aimdk_msgs::msg::MessageHeader();
46
47    float left_position = position_pairs_[current_index_].first;
48    float right_position = position_pairs_[current_index_].second;
49
50    aimdk_msgs::msg::HandCommand left_hands;
51    left_hands.name = "left_hand";
52    left_hands.position = left_position;
53    left_hands.velocity = 1.0;
54    left_hands.acceleration = 1.0;
55    left_hands.deceleration = 1.0;
56    left_hands.effort = 1.0;
57
58    aimdk_msgs::msg::HandCommand right_hands;
59    right_hands.name = "right_hand";
60    right_hands.position = right_position;
61    right_hands.velocity = 1.0;
62    right_hands.acceleration = 1.0;
63    right_hands.deceleration = 1.0;
64    right_hands.effort = 1.0;
65
66    msg->left_hands.push_back(left_hands);
67    msg->right_hands.push_back(right_hands);
68    msg->left_hand_type.value = 2;
69    msg->right_hand_type.value = 2;
70
71    publisher_->publish(std::move(msg));
72  }
73
74private:
75  rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
76  rclcpp::TimerBase::SharedPtr timer_;
77
78  std::vector<std::pair<float, float>> position_pairs_;
79  size_t current_index_;
80
81  rclcpp::Time last_switch_time_;
82};
83
84int main(int argc, char *argv[]) {
85  rclcpp::init(argc, argv);
86  auto hand_control_node = std::make_shared<HandControl>();
87  rclcpp::spin(hand_control_node);
88  rclcpp::shutdown();
89  return 0;
90}

6.2.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 <chrono>
  3#include <cmath>
  4#include <memory>
  5#include <rclcpp/rclcpp.hpp>
  6
  7using namespace std::chrono_literals;
  8
  9class HandCommandPublisher : public rclcpp::Node {
 10public:
 11  HandCommandPublisher() : Node("hand_command_publisher") {
 12    publisher_ = this->create_publisher<aimdk_msgs::msg::HandCommandArray>(
 13        "/aima/hal/joint/hand/command", 10);
 14
 15    // Create a timer to publish once per second
 16    timer_ = this->create_wall_timer(
 17        1s, std::bind(&HandCommandPublisher::publish_command, this));
 18  }
 19
 20private:
 21  void publish_command() {
 22    auto message = aimdk_msgs::msg::HandCommandArray();
 23
 24    // Set hander
 25    message.header.stamp = this->now();
 26    message.header.frame_id = "hand_command";
 27
 28    // Set the hand type
 29    message.left_hand_type.value = 1;  // NIMBLE_HANDS
 30    message.right_hand_type.value = 1; // NIMBLE_HANDS
 31
 32    // Create left hand command array
 33    message.left_hands.resize(10);
 34
 35    // Set left thumb
 36    message.left_hands[0].name = "left_thumb";
 37    message.left_hands[0].position = 0.0;
 38    message.left_hands[0].velocity = 0.1;
 39    message.left_hands[0].acceleration = 0.0;
 40    message.left_hands[0].deceleration = 0.0;
 41    message.left_hands[0].effort = 0.0;
 42    // Set other left fingers
 43    for (int i = 1; i < 10; i++) {
 44      message.left_hands[i].name = "left_index";
 45      message.left_hands[i].position = 0.0;
 46      message.left_hands[i].velocity = 0.1;
 47      message.left_hands[i].acceleration = 0.0;
 48      message.left_hands[i].deceleration = 0.0;
 49      message.left_hands[i].effort = 0.0;
 50    }
 51
 52    // Create right hand command array
 53    message.right_hands.resize(10);
 54
 55    // Set right thumb
 56    message.right_hands[0].name = "right_thumb";
 57    message.right_hands[0].position = 0.0;
 58    message.right_hands[0].velocity = 0.1;
 59    message.right_hands[0].acceleration = 0.0;
 60    message.right_hands[0].deceleration = 0.0;
 61    message.right_hands[0].effort = 0.0;
 62
 63    // Set other right fingers (pinky)
 64    for (int i = 1; i < 10; i++) {
 65      message.right_hands[i].name = "right_pinky";
 66      message.right_hands[i].position = 0.0;
 67      message.right_hands[i].velocity = 0.1;
 68      message.right_hands[i].acceleration = 0.0;
 69      message.right_hands[i].deceleration = 0.0;
 70      message.right_hands[i].effort = 0.0;
 71    }
 72
 73    if (target_finger <= 10) {
 74      message.right_hands[target_finger].position = 0.8;
 75    } else {
 76      int target_finger_ = target_finger - 10;
 77      double target_position = 0.8;
 78      if (target_finger_ < 3) {
 79        // The three thumb motors on the left hand need their signs inverted to
 80        // mirror the right hand's motion
 81        target_position = -target_position;
 82      }
 83      message.left_hands[target_finger_].position = target_position;
 84    }
 85
 86    // Publish the message
 87    publisher_->publish(message);
 88
 89    RCLCPP_INFO(this->get_logger(),
 90                "Published hand command with target_finger: %d", target_finger);
 91
 92    update_target_finger();
 93  }
 94
 95  void update_target_finger() {
 96    if (increasing_) {
 97      target_finger += step_;
 98      if (target_finger >= 19) {
 99        target_finger = 19;
100        increasing_ = false;
101      }
102    } else {
103      target_finger -= step_;
104      if (target_finger <= 0) {
105        target_finger = 0;
106        increasing_ = true;
107      }
108    }
109  }
110
111  rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
112  rclcpp::TimerBase::SharedPtr timer_;
113
114  int target_finger = 0;
115  int step_ = 1;
116  bool increasing_ = true;
117};
118
119int main(int argc, char **argv) {
120  rclcpp::init(argc, argv);
121  auto node = std::make_shared<HandCommandPublisher>();
122  rclcpp::spin(node);
123  rclcpp::shutdown();
124  return 0;
125}

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 <chrono>
  7#include <iomanip>
  8#include <iostream>
  9#include <rclcpp/rclcpp.hpp>
 10
 11using namespace std::chrono_literals;
 12
 13class HandStateSubscriber : public rclcpp::Node {
 14public:
 15  HandStateSubscriber() : Node("hand_state_subscriber") {
 16    publisher_ = this->create_publisher<aimdk_msgs::msg::HandCommandArray>(
 17        "/aima/hal/joint/hand/command", 10);
 18    rclcpp::QoS qos_profile(rclcpp::KeepLast(10));
 19    qos_profile.best_effort(); // Set reliability to BEST_EFFORT
 20    // Create subscriber
 21    subscription_ = this->create_subscription<aimdk_msgs::msg::HandStateArray>(
 22        "/aima/hal/joint/hand/state", qos_profile,
 23        std::bind(&HandStateSubscriber::topic_callback, this,
 24                  std::placeholders::_1));
 25    // Create a timer to publish once per second
 26    timer_ = this->create_wall_timer(
 27        1s, std::bind(&HandStateSubscriber::publish_command, this));
 28    RCLCPP_INFO(
 29        this->get_logger(),
 30        "Subscriber started, listening to /aima/hal/joint/hand/state topic...");
 31  }
 32
 33private:
 34  void topic_callback(const aimdk_msgs::msg::HandStateArray::SharedPtr msg) {
 35    // Print message header information
 36    RCLCPP_INFO(this->get_logger(),
 37                "Message received - Sequence: %u, Timestamp: %d.%09d",
 38                msg->header.sequence, msg->header.stamp.sec,
 39                msg->header.stamp.nanosec);
 40
 41    // Print left hand touch sensor data
 42    print_touch_sensor_data("Left Hand", msg->left_touch_sensors);
 43
 44    // Print right hand touch sensor data
 45    print_touch_sensor_data("Right Hand", msg->right_touch_sensors);
 46
 47    std::cout << std::endl;
 48  }
 49
 50  /**
 51   * @brief Print touch sensor data for a specific hand
 52   * @param hand_name Name of the hand (Left Hand/Right Hand)
 53   * @param sensor_data Touch sensor data structure
 54   */
 55  void print_touch_sensor_data(
 56      const std::string &hand_name,
 57      const aimdk_msgs::msg::HandTouchSensorData &sensor_data) {
 58    std::cout << "=== " << hand_name << " Touch Sensor Data ===" << std::endl;
 59
 60    // Print palm touch data
 61    std::cout << "Palm Touch Data (36 elements): ";
 62    print_array(sensor_data.palm_touch_data);
 63
 64    // Print back of hand touch data
 65    std::cout << "Back of Hand Touch Data (36 elements): ";
 66    print_array(sensor_data.back_of_hand_touch_data);
 67
 68    // Print finger touch data
 69    std::cout << "Thumb Touch Data (16 elements): ";
 70    print_array(sensor_data.thumb_touch_data);
 71
 72    std::cout << "Index Finger Touch Data (16 elements): ";
 73    print_array(sensor_data.index_finger_touch_data);
 74
 75    std::cout << "Middle Finger Touch Data (16 elements): ";
 76    print_array(sensor_data.middle_finger_touch_data);
 77
 78    std::cout << "Ring Finger Touch Data (16 elements): ";
 79    print_array(sensor_data.ring_finger_touch_data);
 80
 81    std::cout << "Little Finger Touch Data (16 elements): ";
 82    print_array(sensor_data.little_finger_touch_data);
 83  }
 84
 85  /**
 86   * @brief Print array of 25 uint8_t elements
 87   * @param arr Array to print
 88   */
 89  void print_array(const std::array<uint8_t, 36> &arr) {
 90    std::cout << "[";
 91    for (size_t i = 0; i < arr.size(); ++i) {
 92      std::cout << std::setw(3) << static_cast<int>(arr[i]);
 93      if (i < arr.size() - 1)
 94        std::cout << " ";
 95    }
 96    std::cout << "]" << std::endl;
 97  }
 98
 99  /**
100   * @brief Print array of 16 uint8_t elements
101   * @param arr Array to print
102   */
103  void print_array(const std::array<uint8_t, 16> &arr) {
104    std::cout << "[";
105    for (size_t i = 0; i < arr.size(); ++i) {
106      std::cout << std::setw(3) << static_cast<int>(arr[i]);
107      if (i < arr.size() - 1)
108        std::cout << " ";
109    }
110    std::cout << "]" << std::endl;
111  }
112
113  void publish_command() {
114    auto message = aimdk_msgs::msg::HandCommandArray();
115
116    // Set hander
117    message.header.stamp = this->now();
118    message.header.frame_id = "hand_command";
119
120    // Set the hand type
121    message.left_hand_type.value = 1;  // NIMBLE_HANDS
122    message.right_hand_type.value = 1; // NIMBLE_HANDS
123
124    // Create left hand command array
125    message.left_hands.resize(10);
126
127    // Set left thumb
128    message.left_hands[0].name = "left_thumb";
129    message.left_hands[0].position = 0.0;
130    message.left_hands[0].velocity = 0.1;
131    message.left_hands[0].acceleration = 0.0;
132    message.left_hands[0].deceleration = 0.0;
133    message.left_hands[0].effort = 0.0;
134    // Set other left fingers
135    for (int i = 1; i < 10; i++) {
136      message.left_hands[i].name = "left_index";
137      message.left_hands[i].position = 0.0;
138      message.left_hands[i].velocity = 0.1;
139      message.left_hands[i].acceleration = 0.0;
140      message.left_hands[i].deceleration = 0.0;
141      message.left_hands[i].effort = 0.0;
142    }
143
144    // Create right hand command array
145    message.right_hands.resize(10);
146
147    // Set right thumb
148    message.right_hands[0].name = "right_thumb";
149    message.right_hands[0].position = 0.0;
150    message.right_hands[0].velocity = 0.1;
151    message.right_hands[0].acceleration = 0.0;
152    message.right_hands[0].deceleration = 0.0;
153    message.right_hands[0].effort = 0.0;
154
155    // Set other right fingers (pinky)
156    for (int i = 1; i < 10; i++) {
157      message.right_hands[i].name = "right_pinky";
158      message.right_hands[i].position = 0.0;
159      message.right_hands[i].velocity = 0.1;
160      message.right_hands[i].acceleration = 0.0;
161      message.right_hands[i].deceleration = 0.0;
162      message.right_hands[i].effort = 0.0;
163    }
164
165    // Publish the message
166    publisher_->publish(message);
167
168    RCLCPP_INFO(this->get_logger(), "Published hand command");
169  }
170
171  rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
172  rclcpp::Subscription<aimdk_msgs::msg::HandStateArray>::SharedPtr
173      subscription_;
174  rclcpp::TimerBase::SharedPtr timer_;
175};
176
177int main(int argc, char *argv[]) {
178  rclcpp::init(argc, argv);
179  auto node = std::make_shared<HandStateSubscriber>();
180  rclcpp::spin(node);
181  rclcpp::shutdown();
182  return 0;
183}

压力传感器位于五个手指的指尖、手心、手背。需要给灵巧手发送至少一个运动指令,才能获得对应的压力数据上报。

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 <memory>
 10#include <signal.h>
 11
 12std::shared_ptr<rclcpp::Node> g_node = nullptr;
 13
 14void signal_handler(int signal) {
 15  if (g_node) {
 16    RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
 17                signal);
 18    g_node.reset();
 19  }
 20  rclcpp::shutdown();
 21  exit(signal);
 22}
 23
 24class McInputClient : public rclcpp::Node {
 25public:
 26  McInputClient() : Node("set_mc_input_source_client") {
 27    client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
 28        "/aimdk_5Fmsgs/srv/SetMcInputSource");
 29
 30    RCLCPP_INFO(this->get_logger(), "✅ SetMcInputSource client node created.");
 31
 32    // Wait for the service to become available
 33    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 34      RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
 35    }
 36    RCLCPP_INFO(this->get_logger(),
 37                "🟢 Service available, ready to send request.");
 38  }
 39
 40  bool send_request() {
 41    try {
 42      auto request =
 43          std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
 44
 45      // Set request data
 46      request->action.value = 1001;         // Add new input source
 47      request->input_source.name = "node";  // Set message source
 48      request->input_source.priority = 40;  // Set priority
 49      request->input_source.timeout = 1000; // Set timeout (ms)
 50
 51      RCLCPP_INFO(this->get_logger(), "📨 Sending input source request: (ID=%d)",
 52                  request->action.value);
 53
 54      auto timeout = std::chrono::milliseconds(250);
 55      for (int i = 0; i < 8; i++) {
 56        // Set header timestamp
 57        request->request.header.stamp = this->now(); // use Node::now()
 58        auto future = client_->async_send_request(request);
 59        auto retcode = rclcpp::spin_until_future_complete(
 60            this->shared_from_this(), future, timeout);
 61        if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
 62          // retry as remote peer is NOT handled well by ROS
 63          RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
 64          continue;
 65        }
 66        // future.done
 67        auto response = future.get();
 68        auto code = response->response.header.code;
 69        if (code == 0) {
 70          RCLCPP_INFO(this->get_logger(),
 71                      "✅ Input source set successfully. task_id=%lu",
 72                      response->response.task_id);
 73          return true;
 74        } else {
 75          RCLCPP_ERROR(
 76              this->get_logger(),
 77              "❌ Input source set failed. ret_code=%ld, task_id=%lu "
 78              "(duplicated ADD? or MODIFY/ENABLE/DISABLE for unknown source?)",
 79              code, response->response.task_id);
 80          return false;
 81        }
 82      }
 83      RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
 84      return false;
 85    } catch (const std::exception &e) {
 86      RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
 87      return false;
 88    }
 89  }
 90
 91private:
 92  rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr client_;
 93};
 94
 95int main(int argc, char *argv[]) {
 96  try {
 97    rclcpp::init(argc, argv);
 98    signal(SIGINT, signal_handler);
 99    signal(SIGTERM, signal_handler);
100
101    g_node = std::make_shared<McInputClient>();
102    auto client = std::dynamic_pointer_cast<McInputClient>(g_node);
103
104    if (client) {
105      client->send_request();
106    }
107
108    g_node.reset();
109    rclcpp::shutdown();
110
111    return 0;
112  } catch (const std::exception &e) {
113    RCLCPP_ERROR(rclcpp::get_logger("main"),
114                 "Program terminated with exception: %s", e.what());
115    return 1;
116  }
117}

6.2.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 <memory>
  7#include <signal.h>
  8
  9// Global node object
 10std::shared_ptr<rclcpp::Node> g_node = nullptr;
 11
 12// Signal handler
 13void signal_handler(int signal) {
 14  if (g_node) {
 15    RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
 16                signal);
 17    g_node.reset();
 18  }
 19  rclcpp::shutdown();
 20  exit(signal);
 21}
 22
 23// Client Class
 24class GetCurrentInputSourceClient : public rclcpp::Node {
 25public:
 26  GetCurrentInputSourceClient() : Node("get_current_input_source_client") {
 27
 28    client_ = this->create_client<aimdk_msgs::srv::GetCurrentInputSource>(
 29        "/aimdk_5Fmsgs/srv/GetCurrentInputSource");
 30
 31    RCLCPP_INFO(this->get_logger(),
 32                "✅ GetCurrentInputSource client node created.");
 33
 34    // Wait for the service to become available
 35    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 36      RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
 37    }
 38    RCLCPP_INFO(this->get_logger(),
 39                "🟢 Service available, ready to send request.");
 40  }
 41
 42  void send_request() {
 43    try {
 44      auto request =
 45          std::make_shared<aimdk_msgs::srv::GetCurrentInputSource::Request>();
 46      request->request = aimdk_msgs::msg::CommonRequest();
 47
 48      RCLCPP_INFO(this->get_logger(),
 49                  "📨 Sending request to get current input source");
 50
 51      auto timeout = std::chrono::milliseconds(250);
 52
 53      for (int i = 0; i < 8; i++) {
 54        request->request.header.stamp = this->now();
 55        auto future = client_->async_send_request(request);
 56        auto retcode = rclcpp::spin_until_future_complete(
 57            this->shared_from_this(), future, timeout);
 58        if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
 59          // retry as remote peer is NOT handled well by ROS
 60          RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
 61          continue;
 62        }
 63        // future.done
 64        auto response = future.get();
 65        if (response->response.header.code == 0) {
 66          RCLCPP_INFO(this->get_logger(),
 67                      "✅ Current input source get successfully:");
 68          RCLCPP_INFO(this->get_logger(), "Name: %s",
 69                      response->input_source.name.c_str());
 70          RCLCPP_INFO(this->get_logger(), "Priority: %d",
 71                      response->input_source.priority);
 72          RCLCPP_INFO(this->get_logger(), "Timeout: %d",
 73                      response->input_source.timeout);
 74        } else {
 75          RCLCPP_WARN(this->get_logger(),
 76                      "❌ Current input source get failed, return code: %ld",
 77                      response->response.header.code);
 78        }
 79        return;
 80      }
 81      RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
 82    } catch (const std::exception &e) {
 83      RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
 84    }
 85  }
 86
 87private:
 88  rclcpp::Client<aimdk_msgs::srv::GetCurrentInputSource>::SharedPtr client_;
 89};
 90
 91int main(int argc, char *argv[]) {
 92  try {
 93    rclcpp::init(argc, argv);
 94
 95    signal(SIGINT, signal_handler);
 96    signal(SIGTERM, signal_handler);
 97
 98    g_node = std::make_shared<GetCurrentInputSourceClient>();
 99    auto client =
100        std::dynamic_pointer_cast<GetCurrentInputSourceClient>(g_node);
101
102    if (client) {
103      client->send_request();
104    }
105
106    g_node.reset();
107    rclcpp::shutdown();
108    return 0;
109  } catch (const std::exception &e) {
110    RCLCPP_ERROR(rclcpp::get_logger("main"),
111                 "Program exited with exception: %s", e.what());
112    return 1;
113  }
114}

使用说明

# 获取当前输入源信息
ros2 run examples get_current_input_source

输出示例

[INFO] [get_current_input_source_client]: 当前输入源: node
[INFO] [get_current_input_source_client]: 优先级: 40
[INFO] [get_current_input_source_client]: 超时时间: 1000

注意事项

  • 确保GetCurrentInputSource服务正常运行

  • 需要在注册输入源之后才能获取到有效信息

  • 状态码为0表示查询成功

6.2.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 <memory>
 13#include <signal.h>
 14#include <thread>
 15
 16class DirectVelocityControl : public rclcpp::Node {
 17public:
 18  DirectVelocityControl() : Node("direct_velocity_control") {
 19    // Create publisher
 20    publisher_ = this->create_publisher<aimdk_msgs::msg::McLocomotionVelocity>(
 21        "/aima/mc/locomotion/velocity", 10);
 22    // Create service client
 23    client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
 24        "/aimdk_5Fmsgs/srv/SetMcInputSource");
 25
 26    // Maximum speed limits
 27    max_forward_speed_ = 1.0; // m/s
 28    max_lateral_speed_ = 1.0; // m/s
 29    max_angular_speed_ = 1.0; // rad/s
 30    // Minimum speed limits (0 is also OK)
 31    min_forward_speed_ = 0.2; // m/s
 32    min_lateral_speed_ = 0.2; // m/s
 33    min_angular_speed_ = 0.1; // rad/s
 34
 35    RCLCPP_INFO(this->get_logger(), "Direct velocity control node started.");
 36  }
 37
 38  void start_publish() {
 39    if (timer_ != nullptr) {
 40      return;
 41    }
 42    // Set timer to periodically publish velocity messages (50Hz)
 43    timer_ = this->create_wall_timer(
 44        std::chrono::milliseconds(20),
 45        std::bind(&DirectVelocityControl::publish_velocity, this));
 46  }
 47
 48  bool register_input_source() {
 49    const std::chrono::seconds timeout(8);
 50    auto start_time = std::chrono::steady_clock::now();
 51    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 52      if (std::chrono::steady_clock::now() - start_time > timeout) {
 53        RCLCPP_ERROR(this->get_logger(), "Waiting for service timed out");
 54        return false;
 55      }
 56      RCLCPP_INFO(this->get_logger(), "Waiting for input source service...");
 57    }
 58
 59    auto request =
 60        std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
 61    request->action.value = 1001;
 62    request->input_source.name = "node";
 63    request->input_source.priority = 40;
 64    request->input_source.timeout = 1000;
 65
 66    auto timeout2 = std::chrono::milliseconds(250);
 67
 68    for (int i = 0; i < 8; i++) {
 69      request->request.header.stamp = this->now();
 70      auto future = client_->async_send_request(request);
 71      auto retcode = rclcpp::spin_until_future_complete(
 72          this->shared_from_this(), future, timeout2);
 73      if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
 74        // retry as remote peer is NOT handled well by ROS
 75        RCLCPP_INFO(this->get_logger(),
 76                    "trying to register input source... [%d]", i);
 77        continue;
 78      }
 79      // future.done
 80      auto response = future.get();
 81      int state = response->response.state.value;
 82      RCLCPP_INFO(this->get_logger(),
 83                  "Set input source succeeded: state=%d, task_id=%lu", state,
 84                  response->response.task_id);
 85      return true;
 86    }
 87    RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out");
 88    return false;
 89  }
 90
 91  void publish_velocity() {
 92    auto msg = std::make_unique<aimdk_msgs::msg::McLocomotionVelocity>();
 93    msg->header = aimdk_msgs::msg::MessageHeader();
 94    msg->header.stamp = this->now();
 95    msg->source = "node"; // Set message source
 96    msg->forward_velocity = forward_velocity_;
 97    msg->lateral_velocity = lateral_velocity_;
 98    msg->angular_velocity = angular_velocity_;
 99
100    publisher_->publish(std::move(msg));
101    RCLCPP_INFO(this->get_logger(),
102                "Published velocity: Forward %.2f m/s, Lateral %.2f m/s, "
103                "Angular %.2f rad/s",
104                forward_velocity_, lateral_velocity_, angular_velocity_);
105  }
106
107  void clear_velocity() {
108    forward_velocity_ = 0.0;
109    lateral_velocity_ = 0.0;
110    angular_velocity_ = 0.0;
111  }
112
113  bool set_forward(double forward) {
114    if (abs(forward) < 0.005) {
115      forward_velocity_ = 0.0;
116      return true;
117    } else if ((abs(forward) > max_forward_speed_) ||
118               (abs(forward) < min_forward_speed_)) {
119      RCLCPP_ERROR(this->get_logger(), "input value out of range, exiting");
120      return false;
121    } else {
122      forward_velocity_ = forward;
123      return true;
124    }
125  }
126
127  bool set_lateral(double lateral) {
128    if (abs(lateral) < 0.005) {
129      lateral_velocity_ = 0.0;
130      return true;
131    } else if ((abs(lateral) > max_lateral_speed_) ||
132               (abs(lateral) < min_lateral_speed_)) {
133      RCLCPP_ERROR(this->get_logger(), "input value out of range, exiting");
134      return false;
135    } else {
136      lateral_velocity_ = lateral;
137      return true;
138    }
139  }
140
141  bool set_angular(double angular) {
142    if (abs(angular) < 0.005) {
143      angular_velocity_ = 0.0;
144      return true;
145    } else if ((abs(angular) > max_angular_speed_) ||
146               (abs(angular) < min_angular_speed_)) {
147      RCLCPP_ERROR(this->get_logger(), "input value out of range, exiting");
148      return false;
149    } else {
150      angular_velocity_ = angular;
151      return true;
152    }
153  }
154
155private:
156  rclcpp::Publisher<aimdk_msgs::msg::McLocomotionVelocity>::SharedPtr
157      publisher_;
158  rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr client_;
159  rclcpp::TimerBase::SharedPtr timer_;
160
161  double forward_velocity_;
162  double lateral_velocity_;
163  double angular_velocity_;
164
165  double max_forward_speed_;
166  double max_lateral_speed_;
167  double max_angular_speed_;
168
169  double min_forward_speed_;
170  double min_lateral_speed_;
171  double min_angular_speed_;
172};
173
174// Signal Processing
175std::shared_ptr<DirectVelocityControl> global_node = nullptr;
176void signal_handler(int sig) {
177  if (global_node) {
178    global_node->clear_velocity();
179    RCLCPP_INFO(global_node->get_logger(),
180                "Received signal %d: clearing velocity and shutting down", sig);
181  }
182  rclcpp::shutdown();
183  exit(sig);
184}
185
186int main(int argc, char *argv[]) {
187  rclcpp::init(argc, argv);
188  signal(SIGINT, signal_handler);
189  signal(SIGTERM, signal_handler);
190
191  global_node = std::make_shared<DirectVelocityControl>();
192  auto node = global_node;
193
194  if (!node->register_input_source()) {
195    RCLCPP_ERROR(node->get_logger(),
196                 "Input source registration failed, exiting");
197    global_node.reset();
198    rclcpp::shutdown();
199    return 1;
200  }
201
202  // get and check control values
203  // notice that mc has thresholds to start movement
204  double forward, lateral, angular;
205  std::cout << "Enter forward speed 0 or ±(0.2 ~ 1.0) m/s: ";
206  std::cin >> forward;
207  if (!node->set_forward(forward)) {
208    return 2;
209  }
210  std::cout << "Enter lateral speed 0 or ±(0.2 ~ 1.0) m/s: ";
211  std::cin >> lateral;
212  if (!node->set_lateral(lateral)) {
213    return 2;
214  }
215  std::cout << "Enter angular speed 0 or ±(0.1 ~ 1.0) rad/s: ";
216  std::cin >> angular;
217  if (!node->set_angular(angular)) {
218    return 2;
219  }
220
221  RCLCPP_INFO(node->get_logger(), "Setting velocity; moving for 5 seconds");
222
223  node->start_publish();
224
225  auto start_time = node->now();
226  while ((node->now() - start_time).seconds() < 5.0) {
227    rclcpp::spin_some(node);
228    std::this_thread::sleep_for(std::chrono::milliseconds(1));
229  }
230
231  node->clear_velocity();
232  RCLCPP_INFO(node->get_logger(), "5 seconds elapsed; robot stopped");
233
234  rclcpp::spin(node);
235  rclcpp::shutdown();
236  return 0;
237}

6.2.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. 调整控制频率:

    • 修改control_timer_的周期(当前为2ms)

  1#include <aimdk_msgs/msg/joint_command_array.hpp>
  2#include <aimdk_msgs/msg/joint_state_array.hpp>
  3#include <atomic>
  4#include <cstdlib>
  5#include <memory>
  6#include <rclcpp/rclcpp.hpp>
  7#include <ruckig/ruckig.hpp>
  8#include <signal.h>
  9#include <string>
 10#include <unordered_map>
 11#include <vector>
 12
 13/**
 14 * @brief Global variables and signal handling
 15 */
 16// Global variables to control program state
 17std::atomic<bool> g_running(true);
 18std::atomic<bool> g_emergency_stop(false);
 19
 20// Signal handler function
 21void signal_handler(int) {
 22  g_running = false;
 23  RCLCPP_INFO(rclcpp::get_logger("main"),
 24              "Received termination signal, shutting down...");
 25}
 26
 27/**
 28 * @brief Robot model definition
 29 */
 30enum class JointArea {
 31  HEAD,  // Head joints
 32  ARM,   // Arm joints
 33  WAIST, // Waist joints
 34  LEG,   // Leg joints
 35};
 36
 37/**
 38 * @brief Joint information structure
 39 */
 40struct JointInfo {
 41  std::string name;   // Joint name
 42  double lower_limit; // Joint angle lower limit
 43  double upper_limit; // Joint angle upper limit
 44  double kp;          // Position control gain
 45  double kd;          // Velocity control gain
 46};
 47
 48/**
 49 * @brief Robot model configuration
 50 * Contains parameters for all joints, enabling or disabling specific joints as
 51 * needed
 52 */
 53std::map<JointArea, std::vector<JointInfo>> robot_model = {
 54    {JointArea::LEG,
 55     {
 56         // Left leg joint configuration
 57         {"left_hip_pitch_joint", -2.704, 2.556, 40.0, 4.0},
 58         {"left_hip_roll_joint", -0.235, 2.906, 40.0, 4.0},
 59         {"left_hip_yaw_joint", -1.684, 3.430, 30.0, 3.0},
 60         {"left_knee_joint", 0.0000, 2.4073, 80.0, 8.0},
 61         {"left_ankle_pitch_joint", -0.803, 0.453, 40.0, 4.0},
 62         {"left_ankle_roll_joint", -0.2625, 0.2625, 20.0, 2.0},
 63         // Right leg joint configuration
 64         {"right_hip_pitch_joint", -2.704, 2.556, 40.0, 4.0},
 65         {"right_hip_roll_joint", -2.906, 0.235, 40.0, 4.0},
 66         {"right_hip_yaw_joint", -3.430, 1.684, 30.0, 3.0},
 67         {"right_knee_joint", 0.0000, 2.4073, 80.0, 8.0},
 68         {"right_ankle_pitch_joint", -0.803, 0.453, 40.0, 4.0},
 69         {"right_ankle_roll_joint", -0.2625, 0.2625, 20.0, 2.0},
 70     }},
 71
 72    {JointArea::WAIST,
 73     {
 74         // Waist joint configuration
 75         {"waist_yaw_joint", -3.43, 2.382, 20.0, 4.0},
 76         {"waist_pitch_joint", -0.314, 0.314, 20.0, 4.0},
 77         {"waist_roll_joint", -0.488, 0.488, 20.0, 4.0},
 78     }},
 79    {JointArea::ARM,
 80     {
 81         // Left arm joint configuration
 82         {"left_shoulder_pitch_joint", -3.08, 2.04, 20.0, 2.0},
 83         {"left_shoulder_roll_joint", -0.061, 2.993, 20.0, 2.0},
 84         {"left_shoulder_yaw_joint", -2.556, 2.556, 20.0, 2.0},
 85         {"left_elbow_joint", -2.3556, 0.0, 20.0, 2.0},
 86         {"left_wrist_yaw_joint", -2.556, 2.556, 20.0, 2.0},
 87         {"left_wrist_pitch_joint", -0.558, 0.558, 20.0, 2.0},
 88         {"left_wrist_roll_joint", -1.571, 0.724, 20.0, 2.0},
 89         // Right arm joint configuration
 90         {"right_shoulder_pitch_joint", -3.08, 2.04, 20.0, 2.0},
 91         {"right_shoulder_roll_joint", -2.993, 0.061, 20.0, 2.0},
 92         {"right_shoulder_yaw_joint", -2.556, 2.556, 20.0, 2.0},
 93         {"right_elbow_joint", -2.3556, 0.0000, 20.0, 2.0},
 94         {"right_wrist_yaw_joint", -2.556, 2.556, 20.0, 2.0},
 95         {"right_wrist_pitch_joint", -0.558, 0.558, 20.0, 2.0},
 96         {"right_wrist_roll_joint", -0.724, 1.571, 20.0, 2.0},
 97     }},
 98    {JointArea::HEAD,
 99     {
100         // Head joint configuration
101         {"head_yaw_joint", -0.366, 0.366, 20.0, 2.0},
102         {"head_pitch_joint", -0.3838, 0.3838, 20.0, 2.0},
103     }},
104};
105
106/**
107 * @brief Joint controller node class
108 * @tparam DOFs Degrees of freedom
109 * @tparam Area Joint area
110 */
111template <int DOFs, JointArea Area>
112class JointControllerNode : public rclcpp::Node {
113public:
114  /**
115   * @brief Constructor
116   * @param node_name Node name
117   * @param sub_topic Subscription topic name
118   * @param pub_topic Publication topic name
119   * @param qos QoS configuration
120   */
121  JointControllerNode(std::string node_name, std::string sub_topic,
122                      std::string pub_topic,
123                      rclcpp::QoS qos = rclcpp::SensorDataQoS())
124      : Node(node_name), ruckig(0.002) {
125    joint_info_ = robot_model[Area];
126    if (joint_info_.size() != DOFs) {
127      RCLCPP_ERROR(this->get_logger(), "Joint count mismatch.");
128      exit(1);
129    }
130
131    // Set motion constraints for Ruckig trajectory planner
132    for (int i = 0; i < DOFs; ++i) {
133      input.max_velocity[i] = 1.0;     // Max velocity limit
134      input.max_acceleration[i] = 1.0; // Max acceleration limit
135      input.max_jerk[i] = 25.0; // Max jerk (change of acceleration) limit
136    }
137
138    // Create joint state subscriber
139    sub_ = this->create_subscription<aimdk_msgs::msg::JointStateArray>(
140        sub_topic, qos,
141        std::bind(&JointControllerNode::JointStateCallback, this,
142                  std::placeholders::_1));
143
144    // Create joint command publisher
145    pub_ = this->create_publisher<aimdk_msgs::msg::JointCommandArray>(pub_topic,
146                                                                      qos);
147  }
148
149private:
150  // Ruckig trajectory planner variables
151  ruckig::Ruckig<DOFs> ruckig;          // Trajectory planner instance
152  ruckig::InputParameter<DOFs> input;   // Input parameters
153  ruckig::OutputParameter<DOFs> output; // Output parameters
154  bool ruckig_initialized_ = false;   // Trajectory planner initialization flag
155  std::vector<JointInfo> joint_info_; // Joint information list
156
157  // ROS communication variables
158  rclcpp::Subscription<aimdk_msgs::msg::JointStateArray>::SharedPtr
159      sub_; // State subscriber
160  rclcpp::Publisher<aimdk_msgs::msg::JointCommandArray>::SharedPtr
161      pub_; // Command publisher
162
163  /**
164   * @brief Joint state callback function
165   * @param msg Joint state message
166   */
167  void
168  JointStateCallback(const aimdk_msgs::msg::JointStateArray::SharedPtr msg) {
169    // Initialize trajectory planner on first state reception
170    if (!ruckig_initialized_) {
171      for (int i = 0; i < DOFs; ++i) {
172        input.current_position[i] = msg->joints[i].position;
173        input.current_velocity[i] = msg->joints[i].velocity;
174        input.current_acceleration[i] = 0.0;
175      }
176      ruckig_initialized_ = true;
177      RCLCPP_INFO(this->get_logger(),
178                  "Ruckig trajectory planner initialization complete");
179    }
180  }
181
182public:
183  /**
184   * @brief Set target joint position
185   * @param joint_name Joint name
186   * @param target_position Target position
187   * @return Whether the target position was successfully set
188   */
189  bool SetTargetPosition(std::string joint_name, double target_position) {
190    if (!ruckig_initialized_) {
191      RCLCPP_WARN(this->get_logger(),
192                  "Ruckig trajectory planner not initialized");
193      return false;
194    }
195
196    // Find target joint and set its position
197    int target_joint = -1;
198    for (int i = 0; i < DOFs; ++i) {
199      if (joint_info_[i].name == joint_name) {
200        // Check if target position is within limits
201        if (target_position < joint_info_[i].lower_limit ||
202            target_position > joint_info_[i].upper_limit) {
203          RCLCPP_ERROR(
204              this->get_logger(),
205              "Target position %.3f exceeds limit for joint %s [%.3f, %.3f]",
206              target_position, joint_name.c_str(), joint_info_[i].lower_limit,
207              joint_info_[i].upper_limit);
208          return false;
209        }
210        input.target_position[i] = target_position;
211        input.target_velocity[i] = 0.0;
212        input.target_acceleration[i] = 0.0;
213        target_joint = i;
214      } else {
215        input.target_position[i] = input.current_position[i];
216        input.target_velocity[i] = 0.0;
217        input.target_acceleration[i] = 0.0;
218      }
219    }
220
221    if (target_joint == -1) {
222      RCLCPP_ERROR(this->get_logger(), "Joint %s not found",
223                   joint_name.c_str());
224      return false;
225    }
226
227    // Perform trajectory planning and send command using Ruckig
228    const double tolerance = 1e-6;
229    while (g_running && rclcpp::ok() && !g_emergency_stop) {
230      auto result = ruckig.update(input, output);
231      if (result != ruckig::Result::Working &&
232          result != ruckig::Result::Finished) {
233        RCLCPP_WARN(this->get_logger(), "Trajectory planning failed");
234        break;
235      }
236
237      // Update current state
238      for (int i = 0; i < DOFs; ++i) {
239        input.current_position[i] = output.new_position[i];
240        input.current_velocity[i] = output.new_velocity[i];
241        input.current_acceleration[i] = output.new_acceleration[i];
242      }
243
244      // Check if target position is reached
245      if (std::abs(output.new_position[target_joint] - target_position) <
246          tolerance) {
247        RCLCPP_INFO(this->get_logger(), "Joint %s reached target position",
248                    joint_name.c_str());
249        break;
250      }
251
252      // Create and send joint command
253      aimdk_msgs::msg::JointCommandArray cmd;
254      cmd.joints.resize(DOFs);
255      for (int i = 0; i < DOFs; ++i) {
256        auto &joint = joint_info_[i];
257        cmd.joints[i].name = joint.name;
258        cmd.joints[i].position = output.new_position[i];
259        cmd.joints[i].velocity = output.new_velocity[i];
260        cmd.joints[i].stiffness = joint.kp;
261        cmd.joints[i].damping = joint.kd;
262      }
263      pub_->publish(cmd);
264
265      // Short delay to avoid excessive CPU usage
266      std::this_thread::sleep_for(std::chrono::milliseconds(2));
267    }
268
269    return true;
270  }
271
272  /**
273   * @brief Safely stop all joints
274   */
275  void safe_stop() {
276    if (!ruckig_initialized_) {
277      RCLCPP_WARN(this->get_logger(), "Ruckig trajectory planner not "
278                                      "initialized, cannot perform safe stop");
279      return;
280    }
281
282    RCLCPP_INFO(this->get_logger(), "Performing safe stop...");
283
284    // Set all joint target positions to current positions
285    for (int i = 0; i < DOFs; ++i) {
286      input.target_position[i] = input.current_position[i];
287      input.target_velocity[i] = 0.0;
288      input.target_acceleration[i] = 0.0;
289    }
290
291    // Send final command to ensure joints stop
292    aimdk_msgs::msg::JointCommandArray cmd;
293    cmd.joints.resize(DOFs);
294    for (int i = 0; i < DOFs; ++i) {
295      auto &joint = joint_info_[i];
296      cmd.joints[i].name = joint.name;
297      cmd.joints[i].position = input.current_position[i];
298      cmd.joints[i].velocity = 0.0;
299      cmd.joints[i].stiffness = joint.kp;
300      cmd.joints[i].damping = joint.kd;
301    }
302    pub_->publish(cmd);
303
304    RCLCPP_INFO(this->get_logger(), "Safe stop complete");
305  }
306
307  /**
308   * @brief Emergency stop for all joints
309   */
310  void emergency_stop() {
311    g_emergency_stop = true;
312    safe_stop();
313    RCLCPP_ERROR(this->get_logger(), "Emergency stop triggered");
314  }
315};
316
317/**
318 * @brief Main function
319 */
320int main(int argc, char *argv[]) {
321  rclcpp::init(argc, argv);
322
323  // Set up signal handling
324  signal(SIGINT, signal_handler);
325  signal(SIGTERM, signal_handler);
326
327  try {
328    // Create leg controller node
329    auto leg_node = std::make_shared<JointControllerNode<12, JointArea::LEG>>(
330        "leg_node", "/aima/hal/joint/leg/state", "/aima/hal/joint/leg/command");
331
332    // Create timer node
333    rclcpp::Node::SharedPtr timer_node =
334        rclcpp::Node::make_shared("timer_node");
335    double position = 0.8;
336
337    // Create timer callback function
338    auto timer = timer_node->create_wall_timer(std::chrono::seconds(3), [&]() {
339      if (!g_running || g_emergency_stop)
340        return; // If the program is shutting down or emergency stopped, do not
341                // execute new actions
342      position = -position;
343      position = 1.3 + position;
344      if (!leg_node->SetTargetPosition("left_knee_joint", position)) {
345        RCLCPP_ERROR(rclcpp::get_logger("main"),
346                     "Failed to set target position");
347      }
348    });
349
350    // Create executor
351    rclcpp::executors::MultiThreadedExecutor executor;
352    executor.add_node(leg_node);
353    executor.add_node(timer_node);
354
355    // Main loop
356    while (g_running && rclcpp::ok() && !g_emergency_stop) {
357      executor.spin_once(std::chrono::milliseconds(100));
358    }
359
360    // Safely stop all joints
361    RCLCPP_INFO(rclcpp::get_logger("main"), "Safely stopping all joints...");
362    leg_node->safe_stop();
363
364    // Wait a short time to ensure command transmission is complete
365    std::this_thread::sleep_for(std::chrono::milliseconds(100));
366
367    // Clean up resources
368    RCLCPP_INFO(rclcpp::get_logger("main"), "Cleaning up resources...");
369    leg_node.reset();
370    timer_node.reset();
371
372  } catch (const std::exception &e) {
373    RCLCPP_ERROR(rclcpp::get_logger("main"), "Exception occurred: %s",
374                 e.what());
375    g_emergency_stop = true;
376  } catch (...) {
377    RCLCPP_ERROR(rclcpp::get_logger("main"), "Unknown exception occurred");
378    g_emergency_stop = true;
379  }
380
381  RCLCPP_INFO(rclcpp::get_logger("main"), "Program exited safely");
382  rclcpp::shutdown();
383  return 0;
384}

6.2.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 <csignal>
 13#include <curses.h>
 14#include <rclcpp/rclcpp.hpp>
 15
 16using aimdk_msgs::msg::McLocomotionVelocity;
 17using std::placeholders::_1;
 18
 19class KeyboardVelocityController : public rclcpp::Node {
 20public:
 21  KeyboardVelocityController()
 22      : Node("keyboard_velocity_controller"), forward_velocity_(0.0),
 23        lateral_velocity_(0.0), angular_velocity_(0.0), step_(0.2),
 24        angular_step_(0.1) {
 25    pub_ = this->create_publisher<McLocomotionVelocity>(
 26        "/aima/mc/locomotion/velocity", 10);
 27    client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
 28        "/aimdk_5Fmsgs/srv/SetMcInputSource");
 29    // Register input source
 30    if (!register_input_source()) {
 31      RCLCPP_ERROR(this->get_logger(),
 32                   "Input source registration failed, exiting");
 33      throw std::runtime_error("Input source registration failed");
 34    }
 35    // Initialize ncurses
 36    initscr();
 37    cbreak();
 38    noecho();
 39    keypad(stdscr, TRUE);
 40    nodelay(stdscr, TRUE);
 41
 42    timer_ = this->create_wall_timer(
 43        std::chrono::milliseconds(50),
 44        std::bind(&KeyboardVelocityController::checkKeyAndPublish, this));
 45
 46    RCLCPP_INFO(this->get_logger(),
 47                "Control started: W/S Forward/Backward | A/D Strafe Left/Right "
 48                "| Q/E Turn Left/Right | Space Stop | ESC Exit");
 49  }
 50
 51  ~KeyboardVelocityController() {
 52    endwin(); // Restore terminal
 53  }
 54
 55private:
 56  rclcpp::Publisher<McLocomotionVelocity>::SharedPtr pub_;
 57  rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr client_;
 58  rclcpp::TimerBase::SharedPtr timer_;
 59
 60  float forward_velocity_, lateral_velocity_, angular_velocity_;
 61  const float step_, angular_step_;
 62
 63  bool register_input_source() {
 64    const std::chrono::seconds srv_timeout(8);
 65    auto start_time = std::chrono::steady_clock::now();
 66    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 67      if (std::chrono::steady_clock::now() - start_time > srv_timeout) {
 68        RCLCPP_ERROR(this->get_logger(), "Waiting for service timed out");
 69        return false;
 70      }
 71      RCLCPP_INFO(this->get_logger(), "Waiting for input source service...");
 72    }
 73
 74    auto request =
 75        std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
 76    request->action.value = 1001;
 77    request->input_source.name = "node";
 78    request->input_source.priority = 40;
 79    request->input_source.timeout = 1000;
 80
 81    auto timeout = std::chrono::milliseconds(250);
 82
 83    for (int i = 0; i < 8; i++) {
 84      request->request.header.stamp = this->now();
 85      auto future = client_->async_send_request(request);
 86      auto retcode = rclcpp::spin_until_future_complete(
 87          this->get_node_base_interface(), future, timeout);
 88      if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
 89        // retry as remote peer is NOT handled well by ROS
 90        RCLCPP_INFO(this->get_logger(),
 91                    "trying to register input source... [%d]", i);
 92        continue;
 93      }
 94      // future.done
 95      auto response = future.get();
 96      int state = response->response.state.value;
 97      RCLCPP_INFO(this->get_logger(),
 98                  "Set input source succeeded: state=%d, task_id=%lu", state,
 99                  response->response.task_id);
100      return true;
101    }
102    RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out");
103    return false;
104  }
105
106  void checkKeyAndPublish() {
107    int ch = getch(); // non-blocking read
108
109    switch (ch) {
110    case ' ': // Space key
111      forward_velocity_ = 0.0;
112      lateral_velocity_ = 0.0;
113      angular_velocity_ = 0.0;
114      break;
115    case 'w':
116      forward_velocity_ = std::min(forward_velocity_ + step_, 1.0f);
117      break;
118    case 's':
119      forward_velocity_ = std::max(forward_velocity_ - step_, -1.0f);
120      break;
121    case 'a':
122      lateral_velocity_ = std::min(lateral_velocity_ + step_, 1.0f);
123      break;
124    case 'd':
125      lateral_velocity_ = std::max(lateral_velocity_ - step_, -1.0f);
126      break;
127    case 'q':
128      angular_velocity_ = std::min(angular_velocity_ + angular_step_, 1.0f);
129      break;
130    case 'e':
131      angular_velocity_ = std::max(angular_velocity_ - angular_step_, -1.0f);
132      break;
133    case 27: // ESC Key
134      RCLCPP_INFO(this->get_logger(), "Exiting control");
135      rclcpp::shutdown();
136      return;
137    }
138
139    auto msg = std::make_unique<McLocomotionVelocity>();
140    msg->header = aimdk_msgs::msg::MessageHeader();
141    msg->header.stamp = this->now();
142    msg->source = "node";
143    msg->forward_velocity = forward_velocity_;
144    msg->lateral_velocity = lateral_velocity_;
145    msg->angular_velocity = angular_velocity_;
146
147    float fwd = forward_velocity_;
148    float lat = lateral_velocity_;
149    float ang = angular_velocity_;
150
151    pub_->publish(std::move(msg));
152
153    // Screen Output
154    clear();
155    mvprintw(0, 0,
156             "W/S: Forward/Backward | A/D: Left/Right Strafe | Q/E: Turn "
157             "Left/Right | Space: Stop | ESC: Exit");
158    mvprintw(2, 0,
159             "Speed Status: Forward: %.2f m/s | Lateral: %.2f m/s | Angular: "
160             "%.2f rad/s",
161             fwd, lat, ang);
162    refresh();
163  }
164};
165
166int main(int argc, char *argv[]) {
167  rclcpp::init(argc, argv);
168  try {
169    auto node = std::make_shared<KeyboardVelocityController>();
170    rclcpp::spin(node);
171  } catch (const std::exception &e) {
172    RCLCPP_FATAL(rclcpp::get_logger("main"),
173                 "Program exited with exception: %s", e.what());
174  }
175  rclcpp::shutdown();
176  return 0;
177}

6.2.13 拍照

该示例中用到了take_photo,在运行节点程序前,修改需要拍照的相机话题,启动节点程序后,会创建/images/目录,将当前帧图像保存在这个目录下。

 1#include <chrono>
 2#include <cv_bridge/cv_bridge.h>
 3#include <filesystem>
 4#include <opencv2/opencv.hpp>
 5#include <rclcpp/rclcpp.hpp>
 6#include <sensor_msgs/msg/image.hpp>
 7#include <string>
 8
 9class SaveOneRaw : public rclcpp::Node {
10public:
11  SaveOneRaw() : Node("save_one_image"), saved_(false) {
12    topic_ = declare_parameter<std::string>(
13        "image_topic", "/aima/hal/sensor/stereo_head_front_left/rgb_image");
14
15    std::filesystem::create_directories("images");
16
17    auto qos = rclcpp::SensorDataQoS(); // BestEffort/Volatile
18    sub_ = create_subscription<sensor_msgs::msg::Image>(
19        topic_, qos, std::bind(&SaveOneRaw::cb, this, std::placeholders::_1));
20
21    RCLCPP_INFO(get_logger(), "Subscribing (raw): %s", topic_.c_str());
22  }
23
24private:
25  void cb(const sensor_msgs::msg::Image::SharedPtr msg) {
26    if (saved_)
27      return;
28
29    try {
30      // Obtain the Mat without copying by not specifying encoding
31      cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
32      cv::Mat img = cvp->image;
33
34      // Convert to BGR for uniform saving
35      if (msg->encoding == "rgb8") {
36        cv::cvtColor(img, img, cv::COLOR_RGB2BGR);
37      } else if (msg->encoding == "mono8") {
38        cv::cvtColor(img, img, cv::COLOR_GRAY2BGR);
39      } // bgr8 Use this directly; add more branches as needed to support
40        // additional encodings.
41
42      auto now = std::chrono::system_clock::now();
43      auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
44                    now.time_since_epoch())
45                    .count();
46      std::string path = "images/frame_" + std::to_string(ms) + ".png";
47
48      if (cv::imwrite(path, img)) {
49        RCLCPP_INFO(get_logger(), "Saved: %s  (%dx%d)", path.c_str(), img.cols,
50                    img.rows);
51        saved_ = true;
52        rclcpp::shutdown();
53      } else {
54        RCLCPP_ERROR(get_logger(), "cv::imwrite failed: %s", path.c_str());
55      }
56    } catch (const std::exception &e) {
57      RCLCPP_ERROR(get_logger(), "raw decode failed: %s", e.what());
58      // Do not set the saved flag; wait for the next frame
59    }
60  }
61
62  std::string topic_;
63  bool saved_;
64  rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_;
65};
66
67int main(int argc, char **argv) {
68  rclcpp::init(argc, argv);
69  rclcpp::spin(std::make_shared<SaveOneRaw>());
70  return 0;
71}

6.2.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 <deque>
  2#include <iomanip>
  3#include <memory>
  4#include <rclcpp/rclcpp.hpp>
  5#include <sensor_msgs/msg/camera_info.hpp>
  6#include <sensor_msgs/msg/compressed_image.hpp>
  7#include <sensor_msgs/msg/image.hpp>
  8#include <sstream>
  9#include <string>
 10#include <vector>
 11
 12// OpenCV headers for image/video writing
 13#include <cv_bridge/cv_bridge.h>
 14#include <opencv2/opencv.hpp>
 15
 16/**
 17 * @brief Example of subscribing to multiple topics for the head depth camera
 18 *
 19 * You can select which topic type to subscribe to via the startup argument
 20 * --ros-args -p topic_type:=<type>:
 21 *   - depth_image: Depth image (sensor_msgs/Image)
 22 *   - rgb_image: RGB image (sensor_msgs/Image)
 23 *   - rgb_image_compressed: RGB compressed image (sensor_msgs/CompressedImage)
 24 *   - rgb_camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
 25 *   - depth_camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
 26 *
 27 * Examples:
 28 *   ros2 run examples echo_camera_rgbd --ros-args -p
 29 * topic_type:=rgb_image ros2
 30 *   run examples echo_camera_rgbd --ros-args -p
 31 * topic_type:=rgb_camera_info
 32 *
 33 * topic_type defaults to "rgb_image"
 34 *
 35 * See individual callbacks for more detailed comments
 36 */
 37class CameraTopicEcho : public rclcpp::Node {
 38public:
 39  CameraTopicEcho() : Node("camera_topic_echo") {
 40    // Select which topic type to subscribe to
 41    topic_type_ = declare_parameter<std::string>("topic_type", "rgb_image");
 42    dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
 43
 44    // Subscribed topics and their message layouts
 45    // 1. /aima/hal/sensor/rgbd_head_front/depth_image
 46    //    - topic_type: depth_image
 47    //    - message type: sensor_msgs::msg::Image
 48    //    - frame_id: rgbd_head_front
 49    //    - contents: depth image
 50    // 2. /aima/hal/sensor/rgbd_head_front/rgb_image
 51    //    - topic_type: rgb_image
 52    //    - message type: sensor_msgs::msg::Image
 53    //    - frame_id: rgbd_head_front
 54    //    - contents: RGB image
 55    // 3. /aima/hal/sensor/rgbd_head_front/rgb_image/compressed
 56    //    - topic_type: rgb_image_compressed
 57    //    - message type: sensor_msgs::msg::CompressedImage
 58    //    - frame_id: rgbd_head_front
 59    //    - contents: RGB compressed image
 60    // 4. /aima/hal/sensor/rgbd_head_front/rgb_camera_info
 61    //    - topic_type: camera_info
 62    //    - message type: sensor_msgs::msg::CameraInfo
 63    //    - frame_id: rgbd_head_front
 64    //    - contents: RGB camera intrinsic parameters
 65    // 5. /aima/hal/sensor/rgbd_head_front/depth_camera_info
 66    //    - topic_type: camera_info
 67    //    - message type: sensor_msgs::msg::CameraInfo
 68    //    - frame_id: rgbd_head_front
 69    //    - contents: RGB camera intrinsic parameters
 70
 71    auto qos = rclcpp::SensorDataQoS();
 72
 73    // Enable depth image subscription
 74    if (topic_type_ == "depth_image") {
 75      topic_name_ = "/aima/hal/sensor/rgbd_head_front/depth_image";
 76      sub_image_ = create_subscription<sensor_msgs::msg::Image>(
 77          topic_name_, qos,
 78          std::bind(&CameraTopicEcho::cb_image, this, std::placeholders::_1));
 79      RCLCPP_INFO(get_logger(), "✅ Subscribing Depth Image: %s",
 80                  topic_name_.c_str());
 81
 82      // Enable RGB image subscription
 83    } else if (topic_type_ == "rgb_image") {
 84      topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_image";
 85      sub_image_ = create_subscription<sensor_msgs::msg::Image>(
 86          topic_name_, qos,
 87          std::bind(&CameraTopicEcho::cb_image, this, std::placeholders::_1));
 88      RCLCPP_INFO(get_logger(), "✅ Subscribing RGB Image: %s",
 89                  topic_name_.c_str());
 90      if (!dump_video_path_.empty()) {
 91        RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
 92                    dump_video_path_.c_str());
 93      }
 94
 95      // Enable RGB compressed image subscription
 96    } else if (topic_type_ == "rgb_image_compressed") {
 97      topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_image/compressed";
 98      sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
 99          topic_name_, qos,
100          std::bind(&CameraTopicEcho::cb_compressed, this,
101                    std::placeholders::_1));
102      RCLCPP_INFO(get_logger(), "✅ Subscribing CompressedImage: %s",
103                  topic_name_.c_str());
104
105      // Enable rgb camera info subscription
106    } else if (topic_type_ == "rgb_camera_info") {
107      topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_camera_info";
108      // RGB-D CameraInfo subscriptions is different with other cameras.
109      // The messages arrive in about 10Hz and SensorDataQoS is enough.
110      sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
111          topic_name_, qos,
112          std::bind(&CameraTopicEcho::cb_camerainfo, this,
113                    std::placeholders::_1));
114      RCLCPP_INFO(get_logger(), "✅ Subscribing RGB CameraInfo: %s",
115                  topic_name_.c_str());
116
117      // Enable depth camera info subscription
118    } else if (topic_type_ == "depth_camera_info") {
119      topic_name_ = "/aima/hal/sensor/rgbd_head_front/depth_camera_info";
120      // RGB-D CameraInfo subscriptions is different with other cameras.
121      // The messages arrive in about 10Hz and SensorDataQoS is enough.
122      sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
123          topic_name_, qos,
124          std::bind(&CameraTopicEcho::cb_camerainfo, this,
125                    std::placeholders::_1));
126      RCLCPP_INFO(get_logger(), "✅ Subscribing Depth CameraInfo: %s",
127                  topic_name_.c_str());
128
129      // Unknown topic_type error
130    } else {
131      RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
132      throw std::runtime_error("Unknown topic_type");
133    }
134  }
135
136  ~CameraTopicEcho() override {
137    if (video_writer_.isOpened()) {
138      video_writer_.release();
139      RCLCPP_INFO(get_logger(), "Video file closed.");
140    }
141  }
142
143private:
144  // Image callback (depth/RGB image)
145  void cb_image(const sensor_msgs::msg::Image::SharedPtr msg) {
146    update_arrivals();
147
148    if (should_print()) {
149      RCLCPP_INFO(get_logger(),
150                  "📸 %s received\n"
151                  "  • frame_id:        %s\n"
152                  "  • stamp (sec):     %.6f\n"
153                  "  • encoding:        %s\n"
154                  "  • size (WxH):      %u x %u\n"
155                  "  • step (bytes/row):%u\n"
156                  "  • is_bigendian:    %u\n"
157                  "  • recv FPS (1s):   %.1f",
158                  topic_type_.c_str(), msg->header.frame_id.c_str(),
159                  rclcpp::Time(msg->header.stamp).seconds(),
160                  msg->encoding.c_str(), msg->width, msg->height, msg->step,
161                  msg->is_bigendian, get_fps());
162    }
163
164    // Video dump is supported only for RGB images
165    if (topic_type_ == "rgb_image" && !dump_video_path_.empty()) {
166      dump_image_to_video(msg);
167    }
168  }
169
170  // CompressedImage callback
171  void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
172    update_arrivals();
173
174    if (should_print()) {
175      RCLCPP_INFO(get_logger(),
176                  "🗜️  CompressedImage received\n"
177                  "  • frame_id:        %s\n"
178                  "  • stamp (sec):     %.6f\n"
179                  "  • format:          %s\n"
180                  "  • data size:       %zu\n"
181                  "  • recv FPS (1s):   %.1f",
182                  msg->header.frame_id.c_str(),
183                  rclcpp::Time(msg->header.stamp).seconds(),
184                  msg->format.c_str(), msg->data.size(), get_fps());
185    }
186  }
187
188  // CameraInfo callback (camera intrinsic parameters)
189  void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
190    // CameraInfo is typically published once; print it once
191    std::ostringstream oss;
192    oss << "📷 " << topic_type_ << " received\n"
193        << "  • frame_id:        " << msg->header.frame_id << "\n"
194        << "  • stamp (sec):     " << rclcpp::Time(msg->header.stamp).seconds()
195        << "\n"
196        << "  • width x height:  " << msg->width << " x " << msg->height << "\n"
197        << "  • distortion_model:" << msg->distortion_model << "\n"
198        << "  • D: [";
199    for (size_t i = 0; i < msg->d.size(); ++i) {
200      oss << msg->d[i];
201      if (i + 1 < msg->d.size())
202        oss << ", ";
203    }
204    oss << "]\n  • K: [";
205    for (int i = 0; i < 9; ++i) {
206      oss << msg->k[i];
207      if (i + 1 < 9)
208        oss << ", ";
209    }
210    oss << "]\n  • R: [";
211    for (int i = 0; i < 9; ++i) {
212      oss << msg->r[i];
213      if (i + 1 < 9)
214        oss << ", ";
215    }
216    oss << "]\n  • P: [";
217    for (int i = 0; i < 12; ++i) {
218      oss << msg->p[i];
219      if (i + 1 < 12)
220        oss << ", ";
221    }
222    oss << "]\n"
223        << "  • binning_x: " << msg->binning_x << "\n"
224        << "  • binning_y: " << msg->binning_y << "\n"
225        << "  • roi: { x_offset: " << msg->roi.x_offset
226        << ", y_offset: " << msg->roi.y_offset
227        << ", height: " << msg->roi.height << ", width: " << msg->roi.width
228        << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
229    RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
230  }
231
232  // Track arrival timestamps to compute FPS
233  void update_arrivals() {
234    const rclcpp::Time now = this->get_clock()->now();
235    arrivals_.push_back(now);
236    while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
237      arrivals_.pop_front();
238    }
239  }
240  double get_fps() const { return static_cast<double>(arrivals_.size()); }
241
242  // Control printing frequency
243  bool should_print() {
244    const rclcpp::Time now = this->get_clock()->now();
245    if ((now - last_print_).seconds() >= 1.0) {
246      last_print_ = now;
247      return true;
248    }
249    return false;
250  }
251
252  // Dump received images to a video file (RGB images only)
253  void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
254    cv::Mat image;
255    try {
256      // Obtain the Mat without copying by not specifying encoding
257      cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
258      image = cvp->image;
259      // Convert to BGR for uniform saving
260      if (msg->encoding == "rgb8") {
261        cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
262      } else {
263        RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
264                    msg->encoding.c_str());
265        return;
266      }
267    } catch (const std::exception &e) {
268      RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
269      return;
270    }
271
272    // Initialize VideoWriter
273    if (!video_writer_.isOpened()) {
274      int fourcc = cv::VideoWriter::fourcc('m', 'p', '4', 'v');
275      double fps = std::max(1.0, get_fps());
276      bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
277                                   cv::Size(image.cols, image.rows), true);
278      if (!ok) {
279        RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
280                     dump_video_path_.c_str());
281        dump_video_path_.clear(); // stop trying
282        return;
283      }
284      RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
285                  dump_video_path_.c_str(), image.cols, image.rows, fps);
286    }
287    video_writer_.write(image);
288  }
289
290  // Member variables
291  std::string topic_type_;
292  std::string topic_name_;
293  std::string dump_video_path_;
294
295  // Subscriptions
296  rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
297  rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
298      sub_compressed_;
299  rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
300
301  // FPS statistics
302  rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
303  std::deque<rclcpp::Time> arrivals_;
304
305  // Video writer
306  cv::VideoWriter video_writer_;
307};
308
309int main(int argc, char **argv) {
310  rclcpp::init(argc, argv);
311  auto node = std::make_shared<CameraTopicEcho>();
312  rclcpp::spin(node);
313  rclcpp::shutdown();
314  return 0;
315}

使用说明:

  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.avi
    

双目相机数据订阅

该示例中用到了echo_camera_stereo,通过订阅/aima/hal/sensor/stereo_head_front_*/话题来接收机器人的双目相机数据,支持左右相机的RGB图、压缩图和相机内参数据。

功能特点:

  • 支持左右相机独立数据订阅

  • 实时FPS统计和数据显示

  • 支持RGB图视频录制功能

  • 可配置的相机选择(左/右)

支持的数据类型:

  • left_rgb_image: 左相机RGB图像 (sensor_msgs/Image)

  • left_rgb_image_compressed: 左相机压缩RGB图像 (sensor_msgs/CompressedImage)

  • left_camera_info: 左相机内参 (sensor_msgs/CameraInfo)

  • right_rgb_image: 右相机RGB图像 (sensor_msgs/Image)

  • right_rgb_image_compressed: 右相机压缩RGB图像 (sensor_msgs/CompressedImage)

  • right_camera_info: 右相机内参 (sensor_msgs/CameraInfo)

  1#include <deque>
  2#include <iomanip>
  3#include <memory>
  4#include <rclcpp/rclcpp.hpp>
  5#include <sensor_msgs/msg/camera_info.hpp>
  6#include <sensor_msgs/msg/compressed_image.hpp>
  7#include <sensor_msgs/msg/image.hpp>
  8#include <sstream>
  9#include <string>
 10#include <vector>
 11
 12// OpenCV headers for image/video writing
 13#include <cv_bridge/cv_bridge.h>
 14#include <opencv2/opencv.hpp>
 15
 16/**
 17 * @brief Example of subscribing to multiple topics for the stereo head camera
 18 *
 19 * You can select which topic type to subscribe to via the startup argument
 20 * --ros-args -p topic_type:=<type>:
 21 *   - left_rgb_image: left camera RGB image (sensor_msgs/Image)
 22 *   - left_rgb_image_compressed: left camera RGB compressed image
 23 * (sensor_msgs/CompressedImage)
 24 *   - left_camera_info: left camera intrinsic parameters
 25 * (sensor_msgs/CameraInfo)
 26 *   - right_rgb_image: right camera RGB image (sensor_msgs/Image)
 27 *   - right_rgb_image_compressed: right camera RGB compressed image
 28 * (sensor_msgs/CompressedImage)
 29 *   - right_camera_info: right camera intrinsic parameters
 30 * (sensor_msgs/CameraInfo)
 31 *
 32 * Examples:
 33 *   ros2 run examples echo_camera_stereo --ros-args -p
 34 * topic_type:=left_rgb_image ros2 run examples echo_camera_stereo --ros-args -p
 35 * topic_type:=right_rgb_image ros2 run examples echo_camera_stereo --ros-args
 36 * -p topic_type:=left_camera_info
 37 *
 38 * topic_type defaults to "left_rgb_image"
 39 *
 40 * See individual callbacks for more detailed comments
 41 */
 42class StereoCameraTopicEcho : public rclcpp::Node {
 43public:
 44  StereoCameraTopicEcho() : Node("stereo_camera_topic_echo") {
 45    // Select which topic type to subscribe to
 46    topic_type_ =
 47        declare_parameter<std::string>("topic_type", "left_rgb_image");
 48    dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
 49
 50    // Subscribed topics and their message layouts
 51    // 1. /aima/hal/sensor/stereo_head_front_left/rgb_image
 52    //    - topic_type: left_rgb_image
 53    //    - message type: sensor_msgs::msg::Image
 54    //    - frame_id: stereo_head_front
 55    //    - child_frame_id: /
 56    //    - contents: left camera raw image
 57    // 2. /aima/hal/sensor/stereo_head_front_left/rgb_image/compressed
 58    //    - topic_type: left_rgb_image_compressed
 59    //    - message type: sensor_msgs::msg::CompressedImage
 60    //    - frame_id: stereo_head_front
 61    //    - contents: left camera compressed image
 62    // 3. /aima/hal/sensor/stereo_head_front_left/camera_info
 63    //    - topic_type: left_camera_info
 64    //    - message type: sensor_msgs::msg::CameraInfo
 65    //    - frame_id: stereo_head_front
 66    //    - contents: left camera intrinsic parameters
 67    // 4. /aima/hal/sensor/stereo_head_front_right/rgb_image
 68    //    - topic_type: right_rgb_image
 69    //    - message type: sensor_msgs::msg::Image
 70    //    - frame_id: stereo_head_front_right
 71    //    - child_frame_id: /
 72    //    - contents: right camera raw image
 73    // 5. /aima/hal/sensor/stereo_head_front_right/rgb_image/compressed
 74    //    - topic_type: right_rgb_image_compressed
 75    //    - message type: sensor_msgs::msg::CompressedImage
 76    //    - frame_id: stereo_head_front_right
 77    //    - contents: right camera compressed image
 78    // 6. /aima/hal/sensor/stereo_head_front_right/camera_info
 79    //    - topic_type: right_camera_info
 80    //    - message type: sensor_msgs::msg::CameraInfo
 81    //    - frame_id: stereo_head_front_right
 82    //    - contents: right camera intrinsic parameters
 83
 84    // Set QoS parameters - use SensorData QoS
 85    auto qos = rclcpp::SensorDataQoS();
 86
 87    // Enable left camera RGB image subscription
 88    if (topic_type_ == "left_rgb_image") {
 89      topic_name_ = "/aima/hal/sensor/stereo_head_front_left/rgb_image";
 90      sub_image_ = create_subscription<sensor_msgs::msg::Image>(
 91          topic_name_, qos,
 92          std::bind(&StereoCameraTopicEcho::cb_image, this,
 93                    std::placeholders::_1));
 94      RCLCPP_INFO(get_logger(), "✅ Subscribing Left RGB Image: %s",
 95                  topic_name_.c_str());
 96      if (!dump_video_path_.empty()) {
 97        RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
 98                    dump_video_path_.c_str());
 99      }
100
101      // Enable left camera RGB compressed image subscription
102    } else if (topic_type_ == "left_rgb_image_compressed") {
103      topic_name_ =
104          "/aima/hal/sensor/stereo_head_front_left/rgb_image/compressed";
105      sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
106          topic_name_, qos,
107          std::bind(&StereoCameraTopicEcho::cb_compressed, this,
108                    std::placeholders::_1));
109      RCLCPP_INFO(get_logger(), "✅ Subscribing Left CompressedImage: %s",
110                  topic_name_.c_str());
111
112      // Enable left camera info subscription
113    } else if (topic_type_ == "left_camera_info") {
114
115      topic_name_ = "/aima/hal/sensor/stereo_head_front_left/camera_info";
116      // CameraInfo subscriptions must use reliable + transient_local
117      // QoS in order to receive latched/history messages (even if only one
118      // message was published). Here we use keep_last(1) + reliable
119      // + transient_local.
120      sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
121          topic_name_,
122          rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
123          std::bind(&StereoCameraTopicEcho::cb_camerainfo, this,
124                    std::placeholders::_1));
125      RCLCPP_INFO(get_logger(),
126                  "✅ Subscribing Left CameraInfo (with transient_local): %s",
127                  topic_name_.c_str());
128
129      // Enable right camera RGB image subscription
130    } else if (topic_type_ == "right_rgb_image") {
131      topic_name_ = "/aima/hal/sensor/stereo_head_front_right/rgb_image";
132      sub_image_ = create_subscription<sensor_msgs::msg::Image>(
133          topic_name_, qos,
134          std::bind(&StereoCameraTopicEcho::cb_image, this,
135                    std::placeholders::_1));
136      RCLCPP_INFO(get_logger(), "✅ Subscribing Right RGB Image: %s",
137                  topic_name_.c_str());
138      if (!dump_video_path_.empty()) {
139        RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
140                    dump_video_path_.c_str());
141      }
142
143      // Enable right camera RGB compressed image subscription
144    } else if (topic_type_ == "right_rgb_image_compressed") {
145      topic_name_ =
146          "/aima/hal/sensor/stereo_head_front_right/rgb_image/compressed";
147      sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
148          topic_name_, qos,
149          std::bind(&StereoCameraTopicEcho::cb_compressed, this,
150                    std::placeholders::_1));
151      RCLCPP_INFO(get_logger(), "✅ Subscribing Right CompressedImage: %s",
152                  topic_name_.c_str());
153
154      // Enable right camera info subscription
155    } else if (topic_type_ == "right_camera_info") {
156      topic_name_ = "/aima/hal/sensor/stereo_head_front_right/camera_info";
157      // CameraInfo subscriptions must use reliable + transient_local
158      // QoS in order to receive latched/history messages (even if only one
159      // message was published). Here we use keep_last(1) + reliable
160      // + transient_local.
161      sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
162          topic_name_,
163          rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
164          std::bind(&StereoCameraTopicEcho::cb_camerainfo, this,
165                    std::placeholders::_1));
166      RCLCPP_INFO(get_logger(),
167                  "✅ Subscribing Right CameraInfo (with transient_local): %s",
168                  topic_name_.c_str());
169
170      // Unknown topic_type error
171    } else {
172      RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
173      throw std::runtime_error("Unknown topic_type");
174    }
175  }
176
177  ~StereoCameraTopicEcho() override {
178    if (video_writer_.isOpened()) {
179      video_writer_.release();
180      RCLCPP_INFO(get_logger(), "Video file closed.");
181    }
182  }
183
184private:
185  // Image callback (left/right RGB image)
186  void cb_image(const sensor_msgs::msg::Image::SharedPtr msg) {
187    update_arrivals();
188
189    if (should_print()) {
190      RCLCPP_INFO(get_logger(),
191                  "📸 %s received\n"
192                  "  • frame_id:        %s\n"
193                  "  • stamp (sec):     %.6f\n"
194                  "  • encoding:        %s\n"
195                  "  • size (WxH):      %u x %u\n"
196                  "  • step (bytes/row):%u\n"
197                  "  • is_bigendian:    %u\n"
198                  "  • recv FPS (1s):   %.1f",
199                  topic_type_.c_str(), msg->header.frame_id.c_str(),
200                  rclcpp::Time(msg->header.stamp).seconds(),
201                  msg->encoding.c_str(), msg->width, msg->height, msg->step,
202                  msg->is_bigendian, get_fps());
203    }
204
205    // Video dump is supported only for RGB images
206    if ((topic_type_ == "left_rgb_image" || topic_type_ == "right_rgb_image") &&
207        !dump_video_path_.empty()) {
208      dump_image_to_video(msg);
209    }
210  }
211
212  // CompressedImage callback (left/right RGB compressed image)
213  void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
214    update_arrivals();
215
216    if (should_print()) {
217      RCLCPP_INFO(get_logger(),
218                  "🗜️  %s received\n"
219                  "  • frame_id:        %s\n"
220                  "  • stamp (sec):     %.6f\n"
221                  "  • format:          %s\n"
222                  "  • data size:       %zu\n"
223                  "  • recv FPS (1s):   %.1f",
224                  topic_type_.c_str(), msg->header.frame_id.c_str(),
225                  rclcpp::Time(msg->header.stamp).seconds(),
226                  msg->format.c_str(), msg->data.size(), get_fps());
227    }
228  }
229
230  // CameraInfo callback (left/right camera intrinsic parameters)
231  void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
232    // CameraInfo is typically published once; print it once
233    std::ostringstream oss;
234    oss << "📷 " << topic_type_ << " received\n"
235        << "  • frame_id:        " << msg->header.frame_id << "\n"
236        << "  • stamp (sec):     " << rclcpp::Time(msg->header.stamp).seconds()
237        << "\n"
238        << "  • width x height:  " << msg->width << " x " << msg->height << "\n"
239        << "  • distortion_model:" << msg->distortion_model << "\n"
240        << "  • D: [";
241    for (size_t i = 0; i < msg->d.size(); ++i) {
242      oss << msg->d[i];
243      if (i + 1 < msg->d.size())
244        oss << ", ";
245    }
246    oss << "]\n  • K: [";
247    for (int i = 0; i < 9; ++i) {
248      oss << msg->k[i];
249      if (i + 1 < 9)
250        oss << ", ";
251    }
252    oss << "]\n  • R: [";
253    for (int i = 0; i < 9; ++i) {
254      oss << msg->r[i];
255      if (i + 1 < 9)
256        oss << ", ";
257    }
258    oss << "]\n  • P: [";
259    for (int i = 0; i < 12; ++i) {
260      oss << msg->p[i];
261      if (i + 1 < 12)
262        oss << ", ";
263    }
264    oss << "]\n"
265        << "  • binning_x: " << msg->binning_x << "\n"
266        << "  • binning_y: " << msg->binning_y << "\n"
267        << "  • roi: { x_offset: " << msg->roi.x_offset
268        << ", y_offset: " << msg->roi.y_offset
269        << ", height: " << msg->roi.height << ", width: " << msg->roi.width
270        << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
271    RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
272  }
273
274  // Track arrival timestamps to compute FPS
275  void update_arrivals() {
276    const rclcpp::Time now = this->get_clock()->now();
277    arrivals_.push_back(now);
278    while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
279      arrivals_.pop_front();
280    }
281  }
282  double get_fps() const { return static_cast<double>(arrivals_.size()); }
283
284  // Control printing frequency
285  bool should_print() {
286    const rclcpp::Time now = this->get_clock()->now();
287    if ((now - last_print_).seconds() >= 1.0) {
288      last_print_ = now;
289      return true;
290    }
291    return false;
292  }
293
294  // Dump received images to a video file (RGB images only)
295  void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
296    cv::Mat image;
297    try {
298      // Obtain the Mat without copying by not specifying encoding
299      cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
300      image = cvp->image;
301      // Convert to BGR for uniform saving
302      if (msg->encoding == "rgb8") {
303        cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
304      } else {
305        RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
306                    msg->encoding.c_str());
307        return;
308      }
309    } catch (const std::exception &e) {
310      RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
311      return;
312    }
313
314    // Initialize VideoWriter
315    if (!video_writer_.isOpened()) {
316      int fourcc = cv::VideoWriter::fourcc('M', 'J', 'P', 'G');
317      double fps = std::max(1.0, get_fps());
318      bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
319                                   cv::Size(image.cols, image.rows), true);
320      if (!ok) {
321        RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
322                     dump_video_path_.c_str());
323        dump_video_path_.clear(); // stop trying
324        return;
325      }
326      RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
327                  dump_video_path_.c_str(), image.cols, image.rows, fps);
328    }
329    video_writer_.write(image);
330  }
331
332  // Member variables
333  std::string topic_type_;
334  std::string topic_name_;
335  std::string dump_video_path_;
336
337  // Subscriptions
338  rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
339  rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
340      sub_compressed_;
341  rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
342
343  // FPS statistics
344  rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
345  std::deque<rclcpp::Time> arrivals_;
346
347  // Video writer
348  cv::VideoWriter video_writer_;
349};
350
351int main(int argc, char **argv) {
352  rclcpp::init(argc, argv);
353  auto node = std::make_shared<StereoCameraTopicEcho>();
354  rclcpp::spin(node);
355  rclcpp::shutdown();
356  return 0;
357}

使用说明:

  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.avi
    

头部后置单目相机数据订阅

该示例中用到了echo_camera_head_rear,通过订阅/aima/hal/sensor/rgb_head_rear/话题来接收机器人的头部后置单目相机数据,支持RGB图(及其mask处理)、压缩图和相机内参数据。

功能特点:

  • 支持头部后置相机数据订阅

  • 实时FPS统计和数据显示

  • 支持RGB图视频录制功能及把手遮挡区域mask处理

  • 可配置的topic类型选择

支持的数据类型:

  • rgb_image: RGB图像 (sensor_msgs/Image)

  • rgb_image_compressed: 压缩RGB图像 (sensor_msgs/CompressedImage)

  • camera_info: 相机内参 (sensor_msgs/CameraInfo)

  1#include <deque>
  2#include <filesystem>
  3#include <iomanip>
  4#include <memory>
  5#include <rclcpp/rclcpp.hpp>
  6#include <sensor_msgs/msg/camera_info.hpp>
  7#include <sensor_msgs/msg/compressed_image.hpp>
  8#include <sensor_msgs/msg/image.hpp>
  9#include <sstream>
 10#include <string>
 11#include <vector>
 12
 13// OpenCV headers for image/video writing
 14#include <cv_bridge/cv_bridge.h>
 15#include <opencv2/opencv.hpp>
 16
 17/**
 18 * @brief Example of subscribing to multiple topics for the rear head monocular
 19 * camera
 20 *
 21 * You can select which topic type to subscribe to via the startup argument
 22 * --ros-args -p topic_type:=<type>:
 23 *   - rgb_image: RGB image (sensor_msgs/Image)
 24 *   - rgb_image_compressed: RGB compressed image (sensor_msgs/CompressedImage)
 25 *   - camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
 26 *
 27 * Examples:
 28 *   ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image
 29 *   ros2 run examples echo_camera_head_rear --ros-args -p
 30 * topic_type:=rgb_image_compressed ros2 run examples echo_camera_head_rear
 31 * --ros-args -p topic_type:=camera_info
 32 *
 33 * topic_type defaults to "rgb_image"
 34 *
 35 * See individual callbacks for more detailed comments
 36 */
 37class HeadRearCameraTopicEcho : public rclcpp::Node {
 38public:
 39  HeadRearCameraTopicEcho() : Node("head_rear_camera_topic_echo") {
 40    // Select which topic type to subscribe to
 41    topic_type_ = declare_parameter<std::string>("topic_type", "rgb_image");
 42    dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
 43    with_mask_ = declare_parameter<bool>("with_mask", false);
 44
 45    // Subscribed topics and their message layouts
 46    // 1. /aima/hal/sensor/rgb_head_rear/rgb_image
 47    //    - topic_type: rgb_image
 48    //    - message type: sensor_msgs::msg::Image
 49    //    - frame_id: rgb_head_rear
 50    //    - child_frame_id: /
 51    //    - contents: raw image data
 52    // 2. /aima/hal/sensor/rgb_head_rear/rgb_image/compressed
 53    //    - topic_type: rgb_image_compressed
 54    //    - message type: sensor_msgs::msg::CompressedImage
 55    //    - frame_id: rgb_head_rear
 56    //    - contents: compressed image data
 57    // 3. /aima/hal/sensor/rgb_head_rear/camera_info
 58    //    - topic_type: camera_info
 59    //    - message type: sensor_msgs::msg::CameraInfo
 60    //    - frame_id: rgb_head_rear
 61    //    - contents: camera intrinsic parameters
 62
 63    // Set QoS parameters - use SensorData QoS
 64    auto qos = rclcpp::SensorDataQoS();
 65
 66    if (with_mask_ && !dump_video_path_.empty()) {
 67      auto mask_path =
 68          std::filesystem::read_symlink("/proc/self/exe").parent_path() /
 69          "data" / "rgb_head_rear_mask.png";
 70      mask_image_ = cv::imread(mask_path, cv::IMREAD_GRAYSCALE);
 71      if (mask_image_.empty()) {
 72        RCLCPP_ERROR(get_logger(), "Failed to load mask file from %s",
 73                     mask_path.c_str());
 74        throw std::runtime_error("Failed to load mask file");
 75      }
 76    }
 77
 78    // Enable RGB image subscription
 79    if (topic_type_ == "rgb_image") {
 80      topic_name_ = "/aima/hal/sensor/rgb_head_rear/rgb_image";
 81      sub_image_ = create_subscription<sensor_msgs::msg::Image>(
 82          topic_name_, qos,
 83          std::bind(&HeadRearCameraTopicEcho::cb_image, this,
 84                    std::placeholders::_1));
 85      RCLCPP_INFO(get_logger(), "✅ Subscribing RGB Image: %s",
 86                  topic_name_.c_str());
 87      if (!dump_video_path_.empty()) {
 88        RCLCPP_INFO(
 89            get_logger(), "📝 Will dump received images %s mask to video: %s",
 90            (with_mask_ ? "with" : "without"), dump_video_path_.c_str());
 91      }
 92    }
 93
 94    // Enable RGB compressed image subscription
 95    else if (topic_type_ == "rgb_image_compressed") {
 96      topic_name_ = "/aima/hal/sensor/rgb_head_rear/rgb_image/compressed";
 97      sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
 98          topic_name_, qos,
 99          std::bind(&HeadRearCameraTopicEcho::cb_compressed, this,
100                    std::placeholders::_1));
101      RCLCPP_INFO(get_logger(), "✅ Subscribing CompressedImage: %s",
102                  topic_name_.c_str());
103
104      // Enable camera info subscription
105    } else if (topic_type_ == "camera_info") {
106      topic_name_ = "/aima/hal/sensor/rgb_head_rear/camera_info";
107      // CameraInfo subscriptions must use reliable + transient_local
108      // QoS in order to receive latched/history messages (even if only one
109      // message was published). Here we use keep_last(1) + reliable
110      // + transient_local.
111      sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
112          topic_name_,
113          rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
114          std::bind(&HeadRearCameraTopicEcho::cb_camerainfo, this,
115                    std::placeholders::_1));
116      RCLCPP_INFO(get_logger(),
117                  "✅ Subscribing CameraInfo (with transient_local): %s",
118                  topic_name_.c_str());
119
120      // Unknown topic_type error
121    } else {
122      RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
123      throw std::runtime_error("Unknown topic_type");
124    }
125  }
126
127  ~HeadRearCameraTopicEcho() override {
128    if (video_writer_.isOpened()) {
129      video_writer_.release();
130      RCLCPP_INFO(get_logger(), "Video file closed.");
131    }
132  }
133
134private:
135  // Image callback (RGB image)
136  void cb_image(const sensor_msgs::msg::Image::SharedPtr msg) {
137    update_arrivals();
138
139    if (should_print()) {
140      RCLCPP_INFO(get_logger(),
141                  "📸 %s received\n"
142                  "  • frame_id:        %s\n"
143                  "  • stamp (sec):     %.6f\n"
144                  "  • encoding:        %s\n"
145                  "  • size (WxH):      %u x %u\n"
146                  "  • step (bytes/row):%u\n"
147                  "  • is_bigendian:    %u\n"
148                  "  • recv FPS (1s):   %.1f",
149                  topic_type_.c_str(), msg->header.frame_id.c_str(),
150                  rclcpp::Time(msg->header.stamp).seconds(),
151                  msg->encoding.c_str(), msg->width, msg->height, msg->step,
152                  msg->is_bigendian, get_fps());
153    }
154
155    // Video dump is supported only for RGB images
156    if (topic_type_ == "rgb_image" && !dump_video_path_.empty()) {
157      dump_image_to_video(msg);
158    }
159  }
160
161  // CompressedImage callback (RGB compressed image)
162  void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
163    update_arrivals();
164
165    if (should_print()) {
166      RCLCPP_INFO(get_logger(),
167                  "🗜️  %s received\n"
168                  "  • frame_id:        %s\n"
169                  "  • stamp (sec):     %.6f\n"
170                  "  • format:          %s\n"
171                  "  • data size:       %zu\n"
172                  "  • recv FPS (1s):   %.1f",
173                  topic_type_.c_str(), msg->header.frame_id.c_str(),
174                  rclcpp::Time(msg->header.stamp).seconds(),
175                  msg->format.c_str(), msg->data.size(), get_fps());
176    }
177  }
178
179  // CameraInfo callback (camera intrinsic parameters)
180  void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
181    // CameraInfo is typically published once; print it once
182    std::ostringstream oss;
183    oss << "📷 " << topic_type_ << " received\n"
184        << "  • frame_id:        " << msg->header.frame_id << "\n"
185        << "  • stamp (sec):     " << rclcpp::Time(msg->header.stamp).seconds()
186        << "\n"
187        << "  • width x height:  " << msg->width << " x " << msg->height << "\n"
188        << "  • distortion_model:" << msg->distortion_model << "\n"
189        << "  • D: [";
190    for (size_t i = 0; i < msg->d.size(); ++i) {
191      oss << msg->d[i];
192      if (i + 1 < msg->d.size())
193        oss << ", ";
194    }
195    oss << "]\n  • K: [";
196    for (int i = 0; i < 9; ++i) {
197      oss << msg->k[i];
198      if (i + 1 < 9)
199        oss << ", ";
200    }
201    oss << "]\n  • R: [";
202    for (int i = 0; i < 9; ++i) {
203      oss << msg->r[i];
204      if (i + 1 < 9)
205        oss << ", ";
206    }
207    oss << "]\n  • P: [";
208    for (int i = 0; i < 12; ++i) {
209      oss << msg->p[i];
210      if (i + 1 < 12)
211        oss << ", ";
212    }
213    oss << "]\n"
214        << "  • binning_x: " << msg->binning_x << "\n"
215        << "  • binning_y: " << msg->binning_y << "\n"
216        << "  • roi: { x_offset: " << msg->roi.x_offset
217        << ", y_offset: " << msg->roi.y_offset
218        << ", height: " << msg->roi.height << ", width: " << msg->roi.width
219        << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
220    RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
221  }
222
223  // Track arrival timestamps to compute FPS
224  void update_arrivals() {
225    const rclcpp::Time now = this->get_clock()->now();
226    arrivals_.push_back(now);
227    while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
228      arrivals_.pop_front();
229    }
230  }
231  double get_fps() const { return static_cast<double>(arrivals_.size()); }
232
233  // Control printing frequency
234  bool should_print() {
235    const rclcpp::Time now = this->get_clock()->now();
236    if ((now - last_print_).seconds() >= 1.0) {
237      last_print_ = now;
238      return true;
239    }
240    return false;
241  }
242
243  // Dump received images to a video file (RGB images only)
244  void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
245    cv::Mat image;
246    try {
247      // Obtain the Mat without copying by not specifying encoding
248      cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
249      image = cvp->image;
250      // Convert to BGR for uniform saving
251      if (msg->encoding == "rgb8") {
252        cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
253      } else {
254        RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
255                    msg->encoding.c_str());
256        return;
257      }
258      if (with_mask_) {
259        image.setTo(cv::Scalar(0, 0, 0), mask_image_ == 0);
260      }
261    } catch (const std::exception &e) {
262      RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
263      return;
264    }
265
266    // Initialize VideoWriter
267    if (!video_writer_.isOpened()) {
268      int fourcc = cv::VideoWriter::fourcc('M', 'J', 'P', 'G');
269      double fps = std::max(1.0, get_fps());
270      bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
271                                   cv::Size(image.cols, image.rows), true);
272      if (!ok) {
273        RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
274                     dump_video_path_.c_str());
275        dump_video_path_.clear(); // stop trying
276        return;
277      }
278      RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
279                  dump_video_path_.c_str(), image.cols, image.rows, fps);
280    }
281    video_writer_.write(image);
282  }
283
284  // Member variables
285  std::string topic_type_;
286  std::string topic_name_;
287  std::string dump_video_path_;
288  bool with_mask_;
289  cv::Mat mask_image_;
290
291  // Subscriptions
292  rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
293  rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
294      sub_compressed_;
295  rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
296
297  // FPS statistics
298  rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
299  std::deque<rclcpp::Time> arrivals_;
300
301  // Video writer
302  cv::VideoWriter video_writer_;
303};
304
305int main(int argc, char **argv) {
306  rclcpp::init(argc, argv);
307  auto node = std::make_shared<HeadRearCameraTopicEcho>();
308  rclcpp::spin(node);
309  rclcpp::shutdown();
310  return 0;
311}

使用说明:

  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.avi
    
  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.avi
    

应用场景:

  • 人脸识别和追踪

  • 目标检测和识别

  • 视觉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
 8class TouchStateSubscriber : public rclcpp::Node {
 9public:
10  TouchStateSubscriber() : Node("touch_state_subscriber") {
11    subscription_ = this->create_subscription<aimdk_msgs::msg::TouchState>(
12        "/aima/hal/sensor/touch_head", 10,
13        std::bind(&TouchStateSubscriber::touch_callback, this,
14                  std::placeholders::_1));
15
16    RCLCPP_INFO(this->get_logger(), "TouchState subscriber started, listening "
17                                    "to /aima/hal/sensor/touch_head");
18  }
19
20private:
21  void touch_callback(const aimdk_msgs::msg::TouchState::SharedPtr msg) {
22    // print message info
23    RCLCPP_INFO(this->get_logger(), "Received TouchState message:");
24    RCLCPP_INFO(this->get_logger(), "  Timestamp: %d.%09d",
25                msg->header.stamp.sec, msg->header.stamp.nanosec);
26
27    std::string event_str = get_event_type_string(msg->event_type);
28    RCLCPP_INFO(this->get_logger(), "  Event Type: %s (%d)", event_str.c_str(),
29                msg->event_type);
30  }
31
32  std::string get_event_type_string(uint8_t event_type) {
33    switch (event_type) {
34    case aimdk_msgs::msg::TouchState::UNKNOWN:
35      return "UNKNOWN";
36    case aimdk_msgs::msg::TouchState::IDLE:
37      return "IDLE";
38    case aimdk_msgs::msg::TouchState::TOUCH:
39      return "TOUCH";
40    case aimdk_msgs::msg::TouchState::SLIDE:
41      return "SLIDE";
42    case aimdk_msgs::msg::TouchState::PAT_ONCE:
43      return "PAT_ONCE";
44    case aimdk_msgs::msg::TouchState::PAT_TWICE:
45      return "PAT_TWICE";
46    case aimdk_msgs::msg::TouchState::PAT_TRIPLE:
47      return "PAT_TRIPLE";
48    default:
49      return "INVALID";
50    }
51  }
52  rclcpp::Subscription<aimdk_msgs::msg::TouchState>::SharedPtr subscription_;
53};
54
55int main(int argc, char *argv[]) {
56  rclcpp::init(argc, argv);
57  auto node = std::make_shared<TouchStateSubscriber>();
58  rclcpp::spin(node);
59  rclcpp::shutdown();
60  return 0;
61}

使用说明:

ros2 run examples echo_head_touch_sensor

输出示例:

[INFO] [1769162721.359354722] [touch_state_subscriber]:   Timestamp: 1769162726.863282315
[INFO] [1769162721.359361643] [touch_state_subscriber]:   Event Type: IDLE (1)
[INFO] [1769167184.142143492] [touch_state_subscriber]:   Timestamp: 1769167189.364879133
[INFO] [1769167184.142147126] [touch_state_subscriber]:   Event Type: TOUCH (2)

6.2.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    auto qos = rclcpp::SensorDataQoS();
 37
 38    if (topic_type_ == "pointcloud") {
 39      topic_name_ = "/aima/hal/sensor/lidar_chest_front/lidar_pointcloud";
 40      sub_pointcloud_ = create_subscription<sensor_msgs::msg::PointCloud2>(
 41          topic_name_, qos,
 42          std::bind(&LidarChestEcho::cb_pointcloud, this,
 43                    std::placeholders::_1));
 44      RCLCPP_INFO(get_logger(), "✅ Subscribing LIDAR PointCloud2: %s",
 45                  topic_name_.c_str());
 46    } else if (topic_type_ == "imu") {
 47      topic_name_ = "/aima/hal/sensor/lidar_chest_front/imu";
 48      sub_imu_ = create_subscription<sensor_msgs::msg::Imu>(
 49          topic_name_, qos,
 50          std::bind(&LidarChestEcho::cb_imu, this, std::placeholders::_1));
 51      RCLCPP_INFO(get_logger(), "✅ Subscribing LIDAR IMU: %s",
 52                  topic_name_.c_str());
 53    } else {
 54      RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
 55      throw std::runtime_error("Unknown topic_type");
 56    }
 57  }
 58
 59private:
 60  // PointCloud2 callback
 61  void cb_pointcloud(const sensor_msgs::msg::PointCloud2::SharedPtr msg) {
 62    update_arrivals();
 63
 64    if (should_print()) {
 65      std::ostringstream oss;
 66      oss << "🟢 LIDAR PointCloud2 received\n"
 67          << "  • frame_id:        " << msg->header.frame_id << "\n"
 68          << "  • stamp (sec):     "
 69          << rclcpp::Time(msg->header.stamp).seconds() << "\n"
 70          << "  • width x height:  " << msg->width << " x " << msg->height
 71          << "\n"
 72          << "  • point_step:      " << msg->point_step << "\n"
 73          << "  • row_step:        " << msg->row_step << "\n"
 74          << "  • fields:          ";
 75      for (const auto &f : msg->fields)
 76        oss << f.name << "(" << (int)f.datatype << ") ";
 77      oss << "\n  • is_bigendian:    " << msg->is_bigendian
 78          << "\n  • is_dense:        " << msg->is_dense
 79          << "\n  • data size:       " << msg->data.size()
 80          << "\n  • recv FPS (1s):   " << get_fps();
 81      RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
 82    }
 83  }
 84
 85  // IMU callback
 86  void cb_imu(const sensor_msgs::msg::Imu::SharedPtr msg) {
 87    update_arrivals();
 88
 89    if (should_print()) {
 90      std::ostringstream oss;
 91      oss << "🟢 LIDAR IMU received\n"
 92          << "  • frame_id:        " << msg->header.frame_id << "\n"
 93          << "  • stamp (sec):     "
 94          << rclcpp::Time(msg->header.stamp).seconds() << "\n"
 95          << "  • orientation:     [" << msg->orientation.x << ", "
 96          << msg->orientation.y << ", " << msg->orientation.z << ", "
 97          << msg->orientation.w << "]\n"
 98          << "  • angular_velocity:[" << msg->angular_velocity.x << ", "
 99          << msg->angular_velocity.y << ", " << msg->angular_velocity.z << "]\n"
100          << "  • linear_accel:    [" << msg->linear_acceleration.x << ", "
101          << msg->linear_acceleration.y << ", " << msg->linear_acceleration.z
102          << "]\n"
103          << "  • recv FPS (1s):   " << get_fps();
104      RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
105    }
106  }
107
108  // Update FPS statistics
109  void update_arrivals() {
110    const rclcpp::Time now = this->get_clock()->now();
111    arrivals_.push_back(now);
112    while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
113      arrivals_.pop_front();
114    }
115  }
116  double get_fps() const { return static_cast<double>(arrivals_.size()); }
117
118  // Control print frequency
119  bool should_print() {
120    const rclcpp::Time now = this->get_clock()->now();
121    if ((now - last_print_).seconds() >= 1.0) {
122      last_print_ = now;
123      return true;
124    }
125    return false;
126  }
127
128  // Member variables
129  std::string topic_type_;
130  std::string topic_name_;
131
132  rclcpp::Subscription<sensor_msgs::msg::PointCloud2>::SharedPtr
133      sub_pointcloud_;
134  rclcpp::Subscription<sensor_msgs::msg::Imu>::SharedPtr sub_imu_;
135
136  rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
137  std::deque<rclcpp::Time> arrivals_;
138};
139
140int main(int argc, char **argv) {
141  rclcpp::init(argc, argv);
142  auto node = std::make_shared<LidarChestEcho>();
143  rclcpp::spin(node);
144  rclcpp::shutdown();
145  return 0;
146}

使用说明:

# 订阅激光雷达点云数据
ros2 run examples echo_lidar_data --ros-args -p topic_type:=pointcloud

# 订阅激光雷达IMU数据
ros2 run examples echo_lidar_data --ros-args -p topic_type:=imu

输出示例:

[INFO] [lidar_chest_echo]: ✅ Subscribing LIDAR PointCloud2: /aima/hal/sensor/lidar_chest_front/lidar_pointcloud
[INFO] [lidar_chest_echo]: 🟢 LIDAR PointCloud2 received
  • frame_id:        lidar_chest_front
  • stamp (sec):     1234567890.123456
  • width x height:  1 x 36000
  • point_step:      16
  • row_step:        16
  • fields:          x(7) y(7) z(7) intensity(7)
  • is_bigendian:    False
  • is_dense:        True
  • data size:       576000
  • recv FPS (1s):   10.0

6.2.17 播放视频

该示例中用到了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 <chrono>
  5#include <memory>
  6#include <signal.h>
  7#include <string>
  8
  9// Global variable used for signal handling
 10std::shared_ptr<rclcpp::Node> g_node = nullptr;
 11
 12// Signal handler function
 13void signal_handler(int signal) {
 14  if (g_node) {
 15    RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
 16                signal);
 17    g_node.reset();
 18  }
 19  rclcpp::shutdown();
 20  exit(signal);
 21}
 22
 23class PlayVideoClient : public rclcpp::Node {
 24public:
 25  PlayVideoClient() : Node("play_video_client") {
 26    client_ = this->create_client<aimdk_msgs::srv::PlayVideo>(
 27        "/face_ui_proxy/play_video");
 28    RCLCPP_INFO(this->get_logger(), "✅ PlayVideo client node started.");
 29
 30    // Wait for the service to become available
 31    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 32      RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
 33    }
 34    RCLCPP_INFO(this->get_logger(),
 35                "🟢 Service available, ready to send request.");
 36  }
 37
 38  bool send_request(const std::string &video_path, uint8_t mode,
 39                    int32_t priority) {
 40    try {
 41      auto request = std::make_shared<aimdk_msgs::srv::PlayVideo::Request>();
 42
 43      request->video_path = video_path;
 44      request->mode = mode;
 45      request->priority = priority;
 46
 47      RCLCPP_INFO(this->get_logger(),
 48                  "📨 Sending request to play video: mode=%hhu video=%s", mode,
 49                  video_path.c_str());
 50
 51      const std::chrono::milliseconds timeout(250);
 52      for (int i = 0; i < 8; i++) {
 53        request->header.header.stamp = this->now();
 54        auto future = client_->async_send_request(request);
 55        auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
 56                                                          future, timeout);
 57        if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
 58          // retry as remote peer is NOT handled well by ROS
 59          RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
 60          continue;
 61        }
 62        // future.done
 63        auto response = future.get();
 64        if (response->success) {
 65          RCLCPP_INFO(this->get_logger(),
 66                      "✅ Request to play video recorded successfully: %s",
 67                      response->message.c_str());
 68          return true;
 69        } else {
 70          RCLCPP_ERROR(this->get_logger(),
 71                       "❌ Failed to record play-video request: %s",
 72                       response->message.c_str());
 73          return false;
 74        }
 75      }
 76      RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
 77      return false;
 78    } catch (const std::exception &e) {
 79      RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
 80      return false;
 81    }
 82  }
 83
 84private:
 85  rclcpp::Client<aimdk_msgs::srv::PlayVideo>::SharedPtr client_;
 86};
 87
 88int main(int argc, char **argv) {
 89  try {
 90    rclcpp::init(argc, argv);
 91
 92    // Set up signal handlers
 93    signal(SIGINT, signal_handler);
 94    signal(SIGTERM, signal_handler);
 95
 96    std::string video_path =
 97        "/agibot/data/home/agi/zhiyuan.mp4"; // Default video path; modify as
 98                                             // needed
 99    int32_t priority = 5;
100    int mode = 2; // Loop playback
101    std::cout << "Enter video play mode (1: once, 2: loop): ";
102    std::cin >> mode;
103    if (mode < 1 || mode > 2) {
104      RCLCPP_ERROR(rclcpp::get_logger("main"), "Invalid play mode: %d", mode);
105      rclcpp::shutdown();
106      return 1;
107    }
108
109    g_node = std::make_shared<PlayVideoClient>();
110    auto client = std::dynamic_pointer_cast<PlayVideoClient>(g_node);
111
112    if (client) {
113      client->send_request(video_path, mode, priority);
114    }
115
116    // Clean up resources
117    g_node.reset();
118    rclcpp::shutdown();
119
120    return 0;
121  } catch (const std::exception &e) {
122    RCLCPP_ERROR(rclcpp::get_logger("main"),
123                 "Program exited with exception: %s", e.what());
124    return 1;
125  }
126}

6.2.18 音频文件播放

该示例中用到了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/srv/play_audio_file.hpp>
  2#include <filesystem>
  3#include <iostream>
  4#include <rclcpp/rclcpp.hpp>
  5#include <string>
  6
  7using PlayAudioFile = aimdk_msgs::srv::PlayAudioFile;
  8
  9int main(int argc, char **argv) {
 10  rclcpp::init(argc, argv);
 11  auto node = rclcpp::Node::make_shared("play_audio_file_client_min");
 12
 13  // 1) Service name
 14  const std::string service_name = "/aimdk_5Fmsgs/srv/PlayAudioFile";
 15  auto client = node->create_client<PlayAudioFile>(service_name);
 16
 17  // 2) Input file path (prompt user if not provided as argument)
 18  std::string default_file =
 19      "/agibot/data/var/interaction/tts_cache/normal/demo.wav";
 20  std::string file_name;
 21
 22  if (argc > 1) {
 23    file_name = argv[1];
 24  } else {
 25    std::cout << "Enter the media file path to play (default: " << default_file
 26              << "): ";
 27    if (!std::getline(std::cin, file_name) || !rclcpp::ok()) {
 28      std::cout << std::endl;
 29      rclcpp::shutdown();
 30      return 1;
 31    }
 32    if (file_name.empty()) {
 33      file_name = default_file;
 34    }
 35  }
 36
 37  // 3) Build the request
 38  // Check if Ctrl+C was pressed during user input
 39  if (!rclcpp::ok()) {
 40    rclcpp::shutdown();
 41    return 1;
 42  }
 43
 44  auto req = std::make_shared<PlayAudioFile::Request>();
 45  req->file.pkg_name = "demo_client";
 46  req->file.file_name = std::filesystem::path(file_name).filename().string();
 47  req->file.file_path =
 48      std::filesystem::path(file_name).parent_path().string() + "/";
 49  req->file.priority = 6;
 50  req->file.priority_weight = 0;
 51
 52  // 4) Wait for service and call
 53  RCLCPP_INFO(rclcpp::get_logger("main"), "Waiting for service: %s",
 54              service_name.c_str());
 55  while (!client->wait_for_service(std::chrono::seconds(2))) {
 56    if (!rclcpp::ok()) {
 57      RCLCPP_ERROR(node->get_logger(),
 58                   "Interrupted while waiting for the service. Exiting.");
 59      rclcpp::shutdown();
 60      return 1;
 61    }
 62    RCLCPP_INFO(node->get_logger(), "⏳ Service unavailable, waiting...");
 63  }
 64
 65  rclcpp::FutureReturnCode rc = rclcpp::FutureReturnCode::TIMEOUT;
 66  std::shared_future<PlayAudioFile::Response::SharedPtr> future;
 67  for (int i = 0; i < 5; ++i) {
 68    req->request.header.stamp = node->now();
 69    future = client->async_send_request(req);
 70    rc = rclcpp::spin_until_future_complete(node, future,
 71                                            std::chrono::seconds(1));
 72    if (rc == rclcpp::FutureReturnCode::SUCCESS) {
 73      break;
 74    }
 75    if (rc == rclcpp::FutureReturnCode::INTERRUPTED) {
 76      // Check if Ctrl+C was pressed during spin_until_future_complete
 77      if (rclcpp::ok()) {
 78        RCLCPP_WARN(node->get_logger(), "Interrupted while waiting");
 79      }
 80      rclcpp::shutdown();
 81      return 1;
 82    }
 83    // retry as remote peer is NOT handled well by ROS
 84    RCLCPP_INFO(node->get_logger(), "trying ... [%d]", i);
 85  }
 86
 87  if (rc != rclcpp::FutureReturnCode::SUCCESS) {
 88    // Check if Ctrl+C was pressed, avoid using node after shutdown
 89    if (rclcpp::ok()) {
 90      RCLCPP_ERROR(node->get_logger(), "Call timed out or did not complete");
 91    }
 92    rclcpp::shutdown();
 93    return 1;
 94  }
 95
 96  // 5) Handle response (success is in reponse.status)
 97  try {
 98    const auto resp = future.get();
 99    bool success = resp->reponse.status.value == 1;
100
101    if (success) {
102      RCLCPP_INFO(node->get_logger(), "✅ Audio file play request succeeded: %s",
103                  file_name.c_str());
104    } else {
105      RCLCPP_ERROR(node->get_logger(), "❌ Audio file play request failed: %s",
106                   file_name.c_str());
107    }
108  } catch (const std::exception &e) {
109    RCLCPP_ERROR(node->get_logger(), "Call exception: %s", e.what());
110  }
111
112  rclcpp::shutdown();
113  return 0;
114}

使用说明:

# 播放默认音频文件
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/interaction/tts_cache/normal/demo.wav

输出示例:

[INFO] [play_audio_file_client_min]: ✅ Audio file play request succeeded

注意事项:

  • 确保音频文件路径正确且文件存在

  • 支持的文件格式:WAV(PCM编码)、RAW PCM,不支持MP3等其他格式

  • 优先级设置影响播放队列顺序

6.2.19 TTS (文字转语音)

该示例中用到了play_tts,通过该节点可实现语音播放输入的文字,用户可根据不同的场景输入相应的文本。

功能特点:

  • 支持命令行参数和交互式输入

  • 完整的服务可用性检查和错误处理

  • 支持优先级控制和打断机制

  • 提供详细的播放状态反馈

核心代码

 1#include <aimdk_msgs/msg/tts_priority_level.hpp>
 2#include <aimdk_msgs/srv/play_tts.hpp>
 3#include <iostream>
 4#include <rclcpp/rclcpp.hpp>
 5#include <string>
 6
 7using PlayTTS = aimdk_msgs::srv::PlayTts;
 8
 9int main(int argc, char **argv) {
10  rclcpp::init(argc, argv);
11  auto node = rclcpp::Node::make_shared("play_tts_client_min");
12
13  const std::string service_name = "/aimdk_5Fmsgs/srv/PlayTts";
14  auto client = node->create_client<PlayTTS>(service_name);
15
16  // Get text to speak
17  std::string tts_text;
18  if (argc > 1) {
19    tts_text = argv[1];
20  } else {
21    std::cout << "Enter text to speak: ";
22    if (!std::getline(std::cin, tts_text) || !rclcpp::ok()) {
23      std::cout << std::endl;
24      rclcpp::shutdown();
25      return 1;
26    }
27    if (tts_text.empty()) {
28      tts_text = "Hello, I am AgiBot X2.";
29    }
30  }
31
32  if (!rclcpp::ok()) {
33    rclcpp::shutdown();
34    return 1;
35  }
36
37  auto req = std::make_shared<PlayTTS::Request>();
38  req->header.header.stamp = node->now();
39  req->tts_req.text = tts_text;
40  req->tts_req.domain = "demo_client"; // Required: identifies the caller
41  req->tts_req.trace_id =
42      "demo"; // Optional: request identifier for the TTS request
43  req->tts_req.is_interrupted =
44      true; // Required: whether to interrupt same-priority playback
45  req->tts_req.priority_weight = 0;
46  req->tts_req.priority_level.value = 6;
47
48  if (!client->wait_for_service(
49          std::chrono::duration_cast<std::chrono::seconds>(
50              std::chrono::seconds(5)))) {
51    if (rclcpp::ok()) {
52      RCLCPP_ERROR(node->get_logger(), "Service unavailable: %s",
53                   service_name.c_str());
54    }
55    rclcpp::shutdown();
56    return 1;
57  }
58
59  auto future = client->async_send_request(req);
60  if (rclcpp::spin_until_future_complete(
61          node, future,
62          std::chrono::duration_cast<std::chrono::seconds>(
63              std::chrono::seconds(10))) != rclcpp::FutureReturnCode::SUCCESS) {
64    if (rclcpp::ok()) {
65      RCLCPP_ERROR(node->get_logger(), "Call timed out");
66    }
67    rclcpp::shutdown();
68    return 1;
69  }
70
71  const auto resp = future.get();
72  if (resp->tts_resp.is_success) {
73    RCLCPP_INFO(node->get_logger(), "✅ TTS play request succeeded");
74  } else {
75    RCLCPP_ERROR(node->get_logger(), "❌ TTS play request failed");
76  }
77
78  rclcpp::shutdown();
79  return 0;
80}

使用说明

# 使用命令行参数播报文本(推荐)
ros2 run examples play_tts "你好,我是灵犀X2机器人"

# 或者不带参数运行,程序会提示用户输入
ros2 run examples play_tts

输出示例

[INFO] [play_tts_client_min]: ✅ 播报请求成功

注意事项

  • 确保TTS服务正常运行

  • 支持中文和英文文本播报

  • 优先级设置影响播放队列顺序

  • 打断功能可中断当前播放的语音

接口参考

  • 服务:/aimdk_5Fmsgs/srv/PlayTts

  • 消息:aimdk_msgs/srv/PlayTts

6.2.20 麦克风数据接收

该示例中用到了mic_receiver,通过订阅/agent/process_audio_output话题来接收机器人的降噪音频数据,支持内置麦克风和外置麦克风两种音频流,并根据VAD(语音活动检测)状态自动保存完整的语音片段为PCM文件。

功能特点:

  • 基于VAD状态自动检测语音开始、处理中、结束

  • 自动保存完整语音片段为PCM格式文件

  • 按时间戳和音频流分类存储

  • 支持音频时长计算和统计信息输出

VAD状态说明:

  • 0: 无语音

  • 1: 语音开始

  • 2: 语音处理中

  • 3: 语音结束

  1#include <aimdk_msgs/msg/audio_vad_state_type.hpp>
  2#include <aimdk_msgs/msg/processed_audio_output.hpp>
  3#include <chrono>
  4#include <ctime>
  5#include <filesystem>
  6#include <fstream>
  7#include <iomanip>
  8#include <rclcpp/rclcpp.hpp>
  9#include <sstream>
 10#include <string>
 11#include <unordered_map>
 12#include <vector>
 13
 14namespace fs = std::filesystem;
 15
 16class AudioSubscriber : public rclcpp::Node {
 17public:
 18  AudioSubscriber() : rclcpp::Node("audio_subscriber") {
 19    // Audio buffers, stored separately by stream_id
 20    // stream_id -> buffer
 21    audio_buffers_ = {};
 22    recording_state_ = {};
 23
 24    audio_output_dir_ = "audio_recordings";
 25    fs::create_directories(audio_output_dir_);
 26
 27    // Note: deep queue to avoid missing data in a burst at start of VAD.
 28    auto qos = rclcpp::QoS(
 29        rclcpp::QoSInitialization::from_rmw(rmw_qos_profile_sensor_data));
 30    qos.keep_last(500).best_effort();
 31
 32    subscription_ =
 33        this->create_subscription<aimdk_msgs::msg::ProcessedAudioOutput>(
 34            "/agent/process_audio_output", qos,
 35            std::bind(&AudioSubscriber::audio_callback, this,
 36                      std::placeholders::_1));
 37
 38    RCLCPP_INFO(this->get_logger(),
 39                "Starting to subscribe to denoised audio data...");
 40  }
 41
 42private:
 43  void
 44  audio_callback(const aimdk_msgs::msg::ProcessedAudioOutput::SharedPtr msg) {
 45    try {
 46      uint32_t stream_id = msg->stream_id;
 47      uint8_t vad_state = msg->audio_vad_state.value;
 48      const std::vector<uint8_t> &audio_data = msg->audio_data;
 49
 50      static const std::unordered_map<uint8_t, std::string> vad_state_names = {
 51          {0, "No Speech"},
 52          {1, "Speech Start"},
 53          {2, "Speech Processing"},
 54          {3, "Speech End"}};
 55      static const std::unordered_map<uint32_t, std::string> stream_names = {
 56          {1, "Internal Microphone"}, {2, "External Microphone"}};
 57
 58      RCLCPP_INFO(this->get_logger(),
 59                  "Audio data received: stream_id=%u, vad_state=%u(%s), "
 60                  "audio_size=%zu bytes",
 61                  stream_id, vad_state,
 62                  vad_state_names.count(vad_state)
 63                      ? vad_state_names.at(vad_state).c_str()
 64                      : "Unknown State",
 65                  audio_data.size());
 66
 67      handle_vad_state(stream_id, vad_state, audio_data);
 68    } catch (const std::exception &e) {
 69      RCLCPP_ERROR(this->get_logger(), "Error processing audio message: %s",
 70                   e.what());
 71    }
 72  }
 73
 74  void handle_vad_state(uint32_t stream_id, uint8_t vad_state,
 75                        const std::vector<uint8_t> &audio_data) {
 76    // Initialize the buffer for this stream_id (if it does not exist)
 77    if (audio_buffers_.count(stream_id) == 0) {
 78      audio_buffers_[stream_id] = std::vector<uint8_t>();
 79      recording_state_[stream_id] = false;
 80    }
 81
 82    static const std::unordered_map<uint8_t, std::string> vad_state_names = {
 83        {0, "No Speech"},
 84        {1, "Speech Start"},
 85        {2, "Speech Processing"},
 86        {3, "Speech End"}};
 87    static const std::unordered_map<uint32_t, std::string> stream_names = {
 88        {1, "Internal Microphone"}, {2, "External Microphone"}};
 89
 90    RCLCPP_INFO(this->get_logger(), "[%s] VAD Atate: %s Audio Data: %zu bytes",
 91                stream_names.count(stream_id)
 92                    ? stream_names.at(stream_id).c_str()
 93                    : ("Unknown Stream " + std::to_string(stream_id)).c_str(),
 94                vad_state_names.count(vad_state)
 95                    ? vad_state_names.at(vad_state).c_str()
 96                    : ("Unknown State" + std::to_string(vad_state)).c_str(),
 97                audio_data.size());
 98
 99    // AUDIO_VAD_STATE_BEGIN
100    if (vad_state == 1) {
101      RCLCPP_INFO(this->get_logger(), "🎤 Speech detected - Start");
102      if (recording_state_[stream_id] == false) {
103        audio_buffers_[stream_id].clear();
104        recording_state_[stream_id] = true;
105      }
106      if (!audio_data.empty()) {
107        audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
108                                         audio_data.begin(), audio_data.end());
109      }
110
111      // AUDIO_VAD_STATE_PROCESSING
112    } else if (vad_state == 2) {
113      RCLCPP_INFO(this->get_logger(), "🔄 Speech Processing...");
114      if (recording_state_[stream_id] && !audio_data.empty()) {
115        audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
116                                         audio_data.begin(), audio_data.end());
117      }
118
119      // AUDIO_VAD_STATE_END
120    } else if (vad_state == 3) {
121      RCLCPP_INFO(this->get_logger(), "✅ Speech End");
122      if (recording_state_[stream_id] && !audio_data.empty()) {
123        audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
124                                         audio_data.begin(), audio_data.end());
125      }
126      if (recording_state_[stream_id] && !audio_buffers_[stream_id].empty()) {
127        save_audio_segment(audio_buffers_[stream_id], stream_id);
128      }
129      recording_state_[stream_id] = false;
130
131      // AUDIO_VAD_STATE_NONE
132    } else if (vad_state == 0) {
133      if (recording_state_[stream_id]) {
134        RCLCPP_INFO(this->get_logger(), "⏹️ Recording state reset");
135        recording_state_[stream_id] = false;
136      }
137    }
138
139    // Output the current buffer status.
140    size_t buffer_size = audio_buffers_[stream_id].size();
141    bool recording = recording_state_[stream_id];
142    RCLCPP_DEBUG(this->get_logger(),
143                 "[Stream %u] Buffer size: %zu bytes, Recording state: %s",
144                 stream_id, buffer_size, recording ? "true" : "false");
145  }
146
147  void save_audio_segment(const std::vector<uint8_t> &audio_data,
148                          uint32_t stream_id) {
149    if (audio_data.empty())
150      return;
151
152    // Get the current timestamp.
153    auto now = std::chrono::system_clock::now();
154    std::time_t t = std::chrono::system_clock::to_time_t(now);
155    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
156                  now.time_since_epoch()) %
157              1000;
158
159    std::ostringstream oss;
160    oss << std::put_time(std::localtime(&t), "%Y%m%d_%H%M%S") << "_"
161        << std::setw(3) << std::setfill('0') << ms.count();
162
163    // Create a subdirectory by stream_id.
164    fs::path stream_dir =
165        fs::path(audio_output_dir_) / ("stream_" + std::to_string(stream_id));
166    fs::create_directories(stream_dir);
167
168    static const std::unordered_map<uint32_t, std::string> stream_names = {
169        {1, "internal_mic"}, {2, "external_mic"}};
170    std::string stream_name = stream_names.count(stream_id)
171                                  ? stream_names.at(stream_id)
172                                  : ("stream_" + std::to_string(stream_id));
173    std::string filename = stream_name + "_" + oss.str() + ".pcm";
174    fs::path filepath = stream_dir / filename;
175
176    try {
177      std::ofstream ofs(filepath, std::ios::binary);
178      ofs.write(reinterpret_cast<const char *>(audio_data.data()),
179                audio_data.size());
180      ofs.close();
181      RCLCPP_INFO(this->get_logger(),
182                  "Audio segment saved: %s (size: %zu bytes)", filepath.c_str(),
183                  audio_data.size());
184
185      // Record audio file duration (assuming 16kHz, 16-bit, mono)
186      int sample_rate = 16000;
187      int bits_per_sample = 16;
188      int channels = 1;
189      int bytes_per_sample = bits_per_sample / 8;
190      size_t total_samples = audio_data.size() / (bytes_per_sample * channels);
191      double duration_seconds =
192          static_cast<double>(total_samples) / sample_rate;
193
194      RCLCPP_INFO(this->get_logger(),
195                  "Audio duration: %.2f seconds (%zu samples)",
196                  duration_seconds, total_samples);
197    } catch (const std::exception &e) {
198      RCLCPP_ERROR(this->get_logger(), "Failed to save audio file: %s",
199                   e.what());
200    }
201  }
202
203  // Member variables
204  std::unordered_map<uint32_t, std::vector<uint8_t>> audio_buffers_;
205  std::unordered_map<uint32_t, bool> recording_state_;
206  std::string audio_output_dir_;
207  rclcpp::Subscription<aimdk_msgs::msg::ProcessedAudioOutput>::SharedPtr
208      subscription_;
209};
210
211int main(int argc, char **argv) {
212  rclcpp::init(argc, argv);
213  auto node = std::make_shared<AudioSubscriber>();
214  RCLCPP_INFO(node->get_logger(),
215              "Listening for denoised audio data, press Ctrl+C to exit...");
216  rclcpp::spin(node);
217  rclcpp::shutdown();
218  return 0;
219}

使用说明:

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

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

    • stream_1/: 内置麦克风音频

    • stream_2/: 外置麦克风音频

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

  4. 音频格式:16kHz, 16位, 单声道PCM

  5. 可通过以下命令播放保存的PCM文件:

    aplay -r 16000 -f S16_LE -c 1 external_mic_20250909_133649_738.pcm
    
  6. 配合唤醒词触发VAD

输出示例:

[INFO] 开始订阅降噪音频数据...
[INFO] 收到音频数据: stream_id=2, vad_state=1(语音开始), audio_size=320 bytes
[INFO] [外置麦克风] VAD状态: 语音开始 音频数据: 320 bytes
[INFO] 🎤 检测到语音开始
[INFO] 收到音频数据: stream_id=2, vad_state=2(语音处理中), audio_size=320 bytes
[INFO] [外置麦克风] VAD状态: 语音处理中 音频数据: 320 bytes
[INFO] 🔄 语音处理中...
[INFO] 收到音频数据: stream_id=2, vad_state=3(语音结束), audio_size=320 bytes
[INFO] [外置麦克风] VAD状态: 语音结束 音频数据: 320 bytes
[INFO] ✅ 语音结束
[INFO] 音频段已保存: audio_recordings/stream_2/external_mic_20250909_133649_738.pcm (大小: 960 bytes)
[INFO] 音频时长: 0.06 秒 (480 样本)

播放PCM音频文件示例 (Linux下使用aplay命令)

假设你已经录制并保存了音频文件 external_mic_20250909_151117_223.pcm, 可以通过如下命令进行播放:

aplay -r 16000 -f S16_LE -c 1 audio_recordings/stream_2/external_mic_20250909_151117_223.pcm

参数说明:

  • -r 16000 # 采样率16kHz

  • -f S16_LE # 16位小端格式

  • -c 1 # 单声道 你也可以用其他音频播放器(如Audacity)以原始PCM格式导入并播放。

注意:如果你保存的是内置麦克风音频,路径应为 audio_recordings/stream_1/internal_mic_xxx.pcm

6.2.21 麦克风原始流式数据接收

该示例中用到了mic_raw_receiver,通过订阅/aima/hal/audio/capture话题来接收机器人的原始音频数据,自动按通道保存为PCM文件。

功能特点:

  • 自动按时间戳创建输出目录

  • 将多通道音频数据按通道拆分保存(麦克风通道/回采通道)

  • 每秒刷新一次缓存数据到磁盘

  • 支持麦克风切换检测

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

  • channel_0_mic.pcm(麦克风)

  • channel_4_ref.pcm(回采)

  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(), "Flashing 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 append: %zu bytes", filepath.c_str(),
133                  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}

使用说明:

ros2 run examples mic_raw_receiver

播放保存的PCM文件:

aplay -r 16000 -f S16_LE -c 1 audio_recordings/20250909_133650_123/channel_0_mic.pcm

6.2.22 原始流式音频播放

该示例中用到了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 = 6;
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      RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
187    }
188
189    auto response = future.get();
190    if (!response) {
191      RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out.");
192      return;
193    }
194    if (response->reponse.status.value ==
195        aimdk_msgs::msg::CommonState::SUCCESS) {
196      focus_ = response->focus_response.focus_gain;
197      RCLCPP_INFO(this->get_logger(), "RequestAudioFocus done: focus %s",
198                  focus_ ? "true" : "false");
199    } else {
200      RCLCPP_ERROR(this->get_logger(),
201                   "Failed in response of RequestAudioFocus: %s",
202                   response->reponse.message.c_str());
203    }
204  }
205
206  void send_focus_release() {
207    auto req = std::make_shared<AbandonAudioFocus::Request>();
208    aimdk_msgs::msg::FocusRequester requester;
209    requester.pkg_name = pkg_name_;
210    req->focus_requester = requester;
211
212    RCLCPP_INFO(this->get_logger(), "Sending AbandonAudioFocus request");
213    std::shared_future<AbandonAudioFocus::Response::SharedPtr> future;
214    for (int i = 0; i < 8; ++i) {
215      future = release_client_->async_send_request(req).future.share();
216      auto status = rclcpp::spin_until_future_complete(
217          shared_from_this(), future, std::chrono::milliseconds(250));
218      if (status == rclcpp::FutureReturnCode::SUCCESS)
219        break;
220      RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
221    }
222
223    auto response = future.get();
224    if (!response) {
225      RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out.");
226      return;
227    }
228    if (response->reponse.status.value ==
229        aimdk_msgs::msg::CommonState::SUCCESS) {
230      // always focus False after abandon
231      focus_ = response->focus_response.focus_gain;
232      RCLCPP_INFO(this->get_logger(), "AbandonAudioFocus done: focus %s",
233                  focus_ ? "true" : "false");
234    } else {
235      RCLCPP_ERROR(this->get_logger(),
236                   "Failed in response of AbandonAudioFocus: %s",
237                   response->reponse.message.c_str());
238    }
239  }
240
241  void
242  focus_event_callback(const aimdk_msgs::msg::FocusResponse::SharedPtr msg) {
243    if (msg->pkg_name == pkg_name_) {
244      RCLCPP_INFO(this->get_logger(),
245                  "receive focus out event: focus state: %s",
246                  msg->focus_gain ? "true" : "false");
247      focus_force_ = msg->focus_gain;
248    }
249  }
250
251  void run_once() {
252    aimdk_msgs::msg::AudioPlayback msg;
253    msg.pkg_name = pkg_name_;
254    msg.token_id = pkg_name_;
255
256    aimdk_msgs::msg::AudioInfo info;
257    info.channels = static_cast<uint8_t>(mic_device_->get_channel_count());
258    info.sample_rate = static_cast<uint32_t>(mic_device_->get_sample_rate());
259
260    aimdk_msgs::msg::AudioData audio_data;
261    audio_data.data = mic_device_->get_cached_data();
262
263    msg.info = info;
264    msg.data = audio_data;
265    pub_->publish(msg);
266  }
267
268  std::string pkg_name_;
269  bool focus_;
270  bool focus_force_;
271  std::unique_ptr<FakeMicDevice> mic_device_;
272  rclcpp::Client<RequestAudioFocus>::SharedPtr request_client_;
273  rclcpp::Client<AbandonAudioFocus>::SharedPtr release_client_;
274  rclcpp::Subscription<aimdk_msgs::msg::FocusResponse>::SharedPtr sub_;
275  rclcpp::Publisher<aimdk_msgs::msg::AudioPlayback>::SharedPtr pub_;
276};
277
278// ---------------------------------------------------------------------------
279// Signal handling
280// ---------------------------------------------------------------------------
281static std::shared_ptr<AudioStreamPlayer> g_node;
282
283void signal_handler(int sig) {
284  if (g_node) {
285    if (g_node->is_holding_focus()) {
286      g_node->send_request(false);
287    }
288    RCLCPP_INFO(g_node->get_logger(),
289                "Received signal %d, abandon audio focus and shutting down",
290                sig);
291  }
292  rclcpp::shutdown();
293}
294
295int main(int argc, char **argv) {
296  rclcpp::init(argc, argv);
297
298  try {
299    auto node = std::make_shared<AudioStreamPlayer>();
300    g_node = node;
301    std::signal(SIGINT, signal_handler);
302    std::signal(SIGTERM, signal_handler);
303
304    node->spin_until_focus();
305    node->run();
306
307  } catch (const std::exception &e) {
308    RCLCPP_ERROR(rclcpp::get_logger("main"),
309                 "Program exited with exception: %s", e.what());
310  }
311
312  if (rclcpp::ok()) {
313    rclcpp::shutdown();
314  }
315  return 0;
316}

使用说明:

# 注意替换/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.23 表情控制

该示例中用到了play_emoji,通过该节点可实现表达指定的表情,用户可根据已有的表情列表来选择要变化的表情,具体表情列表可参考表情列表

  1#include "aimdk_msgs/srv/play_emoji.hpp"
  2#include "aimdk_msgs/msg/common_request.hpp"
  3#include "rclcpp/rclcpp.hpp"
  4#include <chrono>
  5#include <memory>
  6#include <signal.h>
  7#include <string>
  8
  9// Global variable used for signal handling
 10std::shared_ptr<rclcpp::Node> g_node = nullptr;
 11
 12// Signal handler function
 13void signal_handler(int signal) {
 14  if (g_node) {
 15    RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
 16                signal);
 17    g_node.reset();
 18  }
 19  rclcpp::shutdown();
 20  exit(signal);
 21}
 22
 23class PlayEmojiClient : public rclcpp::Node {
 24public:
 25  PlayEmojiClient() : Node("play_emoji_client") {
 26    client_ = this->create_client<aimdk_msgs::srv::PlayEmoji>(
 27        "/face_ui_proxy/play_emoji");
 28    RCLCPP_INFO(this->get_logger(), "✅ PlayEmoji client node started.");
 29
 30    // Wait for the service to become available
 31    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 32      RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
 33    }
 34    RCLCPP_INFO(this->get_logger(),
 35                "🟢 Service available, ready to send request.");
 36  }
 37
 38  bool send_request(uint8_t emoji, uint8_t mode, int32_t priority) {
 39    try {
 40      auto request = std::make_shared<aimdk_msgs::srv::PlayEmoji::Request>();
 41
 42      request->emotion_id = emoji;
 43      request->mode = mode;
 44      request->priority = priority;
 45
 46      RCLCPP_INFO(
 47          this->get_logger(),
 48          "📨 Sending request to play emoji: id=%hhu, mode=%hhu, priority=%d",
 49          emoji, mode, priority);
 50
 51      const std::chrono::milliseconds timeout(250);
 52      for (int i = 0; i < 8; i++) {
 53        request->header.header.stamp = this->now();
 54        auto future = client_->async_send_request(request);
 55        auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
 56                                                          future, timeout);
 57        if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
 58          // retry as remote peer is NOT handled well by ROS
 59          RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
 60          continue;
 61        }
 62        // future.done
 63        auto response = future.get();
 64        if (response->success) {
 65          RCLCPP_INFO(this->get_logger(),
 66                      "✅ Request to play emoji recorded successfully: %s",
 67                      response->message.c_str());
 68          return true;
 69        } else {
 70          RCLCPP_ERROR(this->get_logger(),
 71                       "❌ Failed to record play-emoji request: %s",
 72                       response->message.c_str());
 73          return false;
 74        }
 75      }
 76      RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
 77      return false;
 78    } catch (const std::exception &e) {
 79      RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
 80      return false;
 81    }
 82  }
 83
 84private:
 85  rclcpp::Client<aimdk_msgs::srv::PlayEmoji>::SharedPtr client_;
 86};
 87
 88int main(int argc, char **argv) {
 89  try {
 90    rclcpp::init(argc, argv);
 91
 92    // Set up signal handlers
 93    signal(SIGINT, signal_handler);
 94    signal(SIGTERM, signal_handler);
 95
 96    int32_t priority = 10;
 97
 98    int emotion = 1; // Expression type, 1 means Blink
 99    std::cout
100        << "Enter expression ID: 1-Blink, 60-Bored, 70-Abnormal, 80-Sleeping, "
101           "90-Happy, 190-Very Angry, 200-Adoration"
102        << std::endl;
103    std::cin >> emotion;
104
105    int mode = 1; // Playback mode, 1 means play once, 2 means loop
106    std::cout << "Enter play mode (1: once, 2: loop): ";
107    std::cin >> mode;
108    if (mode < 1 || mode > 2) {
109      RCLCPP_ERROR(rclcpp::get_logger("main"), "Invalid play mode: %d", mode);
110      rclcpp::shutdown();
111      return 1;
112    }
113
114    g_node = std::make_shared<PlayEmojiClient>();
115    auto client = std::dynamic_pointer_cast<PlayEmojiClient>(g_node);
116
117    if (client) {
118      client->send_request(emotion, mode, priority);
119    }
120
121    // Clean up resources
122    g_node.reset();
123    rclcpp::shutdown();
124
125    return 0;
126  } catch (const std::exception &e) {
127    RCLCPP_ERROR(rclcpp::get_logger("main"),
128                 "Program exited with exception: %s", e.what());
129    return 1;
130  }
131}

6.2.24 地图构建

该示例中用到了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.25 使用地图进行重定位

该示例中用到地图构建步骤完成构建的地图进行定位

  1#include <chrono>
  2#include <functional>
  3#include <geometry_msgs/msg/pose.hpp>
  4#include <nav_msgs/msg/odometry.hpp>
  5#include <rclcpp/rclcpp.hpp>
  6#include <std_msgs/msg/string.hpp>
  7
  8using namespace std::chrono_literals;
  9
 10class RelocalizationNode : public rclcpp::Node {
 11public:
 12  RelocalizationNode() : Node("relocalization_node") {
 13    // /integrated_command subscriber requires TRANSIENT_LOCAL durability
 14    auto command_qos = rclcpp::QoS(rclcpp::KeepLast(10));
 15    command_qos.transient_local();
 16
 17    integrated_command_pub_ = this->create_publisher<std_msgs::msg::String>(
 18        "/integrated_command", command_qos);
 19    relocalization_pose_pub_ = this->create_publisher<geometry_msgs::msg::Pose>(
 20        "/relocalization_pose", 10);
 21
 22    // Create subscriber with BEST_EFFORT QoS
 23    auto lidar_loc_qos = rclcpp::QoS(rclcpp::KeepLast(10));
 24    lidar_loc_qos.best_effort();
 25
 26    odometry_sub_ = this->create_subscription<nav_msgs::msg::Odometry>(
 27        "/slam/lidar_odom", lidar_loc_qos,
 28        std::bind(&RelocalizationNode::odometryCallback, this,
 29                  std::placeholders::_1));
 30
 31    // Wait for subscribers before publishing
 32    publish_timer_ = this->create_wall_timer(200ms, [this]() {
 33      // Wait until both publishers have at least one subscriber
 34      if (integrated_command_pub_->get_subscription_count() == 0 ||
 35          relocalization_pose_pub_->get_subscription_count() == 0) {
 36        RCLCPP_INFO_THROTTLE(
 37            this->get_logger(), *this->get_clock(), 2000,
 38            "Waiting for subscribers... (command: %zu, pose: %zu)",
 39            integrated_command_pub_->get_subscription_count(),
 40            relocalization_pose_pub_->get_subscription_count());
 41        return;
 42      }
 43      publish_timer_->cancel();
 44
 45      // Publish integrated_command
 46      auto integrated_command_msg = std_msgs::msg::String();
 47      integrated_command_msg.data = "start_relocalization:1774430080403";
 48      integrated_command_pub_->publish(integrated_command_msg);
 49      RCLCPP_INFO(this->get_logger(), "Published integrated_command");
 50
 51      // Schedule relocalization_pose publication after delay
 52      this->relocalization_pose_timer_ = this->create_wall_timer(1s, [this]() {
 53        this->relocalization_pose_timer_->cancel();
 54
 55        auto relocalization_pose_msg = geometry_msgs::msg::Pose();
 56        relocalization_pose_msg.position.x = 273;
 57        relocalization_pose_msg.position.y = 200;
 58        relocalization_pose_msg.position.z = 0.0;
 59        relocalization_pose_msg.orientation.x = 0.0;
 60        relocalization_pose_msg.orientation.y = 0.0;
 61        relocalization_pose_msg.orientation.z = 0.0;
 62        relocalization_pose_msg.orientation.w = 1.0;
 63
 64        relocalization_pose_pub_->publish(relocalization_pose_msg);
 65        RCLCPP_INFO(this->get_logger(), "Published relocalization_pose");
 66
 67        // Start timeout timer (5 seconds)
 68        this->timeout_timer_ = this->create_wall_timer(30s, [this]() {
 69          if (!success_received_) {
 70            RCLCPP_ERROR(this->get_logger(),
 71                         "Timeout reached - Relocalization failed!");
 72            rclcpp::shutdown();
 73          }
 74        });
 75
 76        RCLCPP_INFO(this->get_logger(),
 77                    "Waiting for robot pose data (timeout: 30s)...");
 78      });
 79    });
 80  }
 81
 82private:
 83  // get robot pose here
 84  void odometryCallback(const nav_msgs::msg::Odometry::SharedPtr msg) {
 85    if (!success_received_) {
 86      success_received_ = true;
 87      if (timeout_timer_) {
 88        timeout_timer_->cancel();
 89      }
 90      RCLCPP_INFO(this->get_logger(),
 91                  "Received odometry data - Relocalization successful!");
 92      rclcpp::shutdown();
 93    }
 94  }
 95
 96  rclcpp::Publisher<std_msgs::msg::String>::SharedPtr integrated_command_pub_;
 97  rclcpp::Publisher<geometry_msgs::msg::Pose>::SharedPtr
 98      relocalization_pose_pub_;
 99  rclcpp::Subscription<nav_msgs::msg::Odometry>::SharedPtr odometry_sub_;
100
101  // Timers
102  rclcpp::TimerBase::SharedPtr publish_timer_;
103  rclcpp::TimerBase::SharedPtr relocalization_pose_timer_;
104  rclcpp::TimerBase::SharedPtr timeout_timer_;
105
106  bool success_received_ = false;
107};
108
109int main(int argc, char *argv[]) {
110  rclcpp::init(argc, argv);
111  auto node = std::make_shared<RelocalizationNode>();
112  rclcpp::spin(node);
113  rclcpp::shutdown();
114  return 0;
115}

使用说明:

ros2 run examples relocate 
[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: 5s)...
[INFO] [1772528945.130242770] [relocalization_node]: Received odometry data - Relocalization successful!

需要将机器人移动到地图构建的起始点,然后运行重定位程序

参数获取方式map_id 与起点像素坐标(position.x / position.y)的获取见 地图标识与坐标的获取

6.2.26 获取已存储的地图

该示例中用到了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 <memory>
 8
 9class MapClient : public rclcpp::Node {
10public:
11  MapClient() : Node("get_map_client") {
12    client_ = this->create_client<aimdk_msgs::srv::GetStoredMapByName>(
13        "/aimdk_5Fmsgs/srv/GetStoredMapByName");
14  }
15
16  void send_request(const std::string &map_name) {
17    while (!client_->wait_for_service(std::chrono::seconds(1))) {
18      if (!rclcpp::ok()) {
19        RCLCPP_ERROR(this->get_logger(),
20                     "Interrupted while waiting for service.");
21        return;
22      }
23      RCLCPP_INFO(this->get_logger(),
24                  "Service not available, waiting again...");
25    }
26
27    auto request =
28        std::make_shared<aimdk_msgs::srv::GetStoredMapByName::Request>();
29
30    request->header.stamp = this->now();
31    request->header.frame_id = "";
32
33    request->map_name = map_name;
34
35    auto result_future = client_->async_send_request(request);
36
37    // Use a fresh executor to spin, avoiding any pre-existing state.
38    rclcpp::executors::SingleThreadedExecutor exec;
39    exec.add_node(shared_from_this());
40    if (exec.spin_until_future_complete(result_future,
41                                        std::chrono::seconds(10)) !=
42        rclcpp::FutureReturnCode::SUCCESS) {
43      RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out.");
44      return;
45    }
46
47    auto result = result_future.get();
48
49    if (result->code == 0) {
50      RCLCPP_INFO(this->get_logger(), "Service call succeeded");
51      RCLCPP_INFO(this->get_logger(), "Map path: %s", result->map_path.c_str());
52      RCLCPP_INFO(this->get_logger(), "Map resolution: %f",
53                  result->map_info.resolution);
54      RCLCPP_INFO(this->get_logger(), "Map width: %u", result->map_info.width);
55      RCLCPP_INFO(this->get_logger(), "Map height: %u",
56                  result->map_info.height);
57      RCLCPP_INFO(this->get_logger(), "Map id: %ld", result->map_id);
58    } else {
59      RCLCPP_ERROR(this->get_logger(), "Service call failed with code: %lu",
60                   result->code);
61    }
62  }
63
64private:
65  rclcpp::Client<aimdk_msgs::srv::GetStoredMapByName>::SharedPtr client_;
66};
67
68int main(int argc, char **argv) {
69  rclcpp::init(argc, argv);
70
71  auto client = std::make_shared<MapClient>();
72  client->send_request("map_name");
73
74  rclcpp::shutdown();
75  return 0;
76}

使用说明:

ros2 run examples get_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: 1

示例默认请求名称为 test_map 的地图,可在代码中修改 send_request 的参数以请求其他地图。

参数获取方式:输入的是地图名称map_name),不是 map_idmap_name 的获取见 地图标识与坐标的获取

6.2.27 播放灵创动作

该示例中用到了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 <iostream>
  8#include <rclcpp/rclcpp.hpp>
  9#include <string>
 10#include <vector>
 11
 12using namespace std::chrono_literals;
 13
 14class RobotResourceClient : public rclcpp::Node {
 15public:
 16  RobotResourceClient() : Node("robot_resource_client") {
 17    get_resource_client_ =
 18        this->create_client<aimdk_msgs::srv::GetRobotResources>(
 19            "/aimdk_5Fmsgs/srv/GetRobotResources");
 20    play_resource_client_ =
 21        this->create_client<aimdk_msgs::srv::ExecuteActionResource>(
 22            "/aimdk_5Fmsgs/srv/ExecuteActionResource");
 23  }
 24
 25  bool get_robot_resources() {
 26    while (!get_resource_client_->wait_for_service(1s)) {
 27      if (!rclcpp::ok()) {
 28        return false;
 29      }
 30      RCLCPP_INFO(this->get_logger(),
 31                  "Service not available, waiting again...");
 32    }
 33
 34    if (!rclcpp::ok()) {
 35      return false;
 36    }
 37
 38    auto request =
 39        std::make_shared<aimdk_msgs::srv::GetRobotResources::Request>();
 40
 41    auto future_result = get_resource_client_->async_send_request(request);
 42
 43    if (rclcpp::spin_until_future_complete(this->get_node_base_interface(),
 44                                           future_result) !=
 45        rclcpp::FutureReturnCode::SUCCESS) {
 46      if (rclcpp::ok()) {
 47        RCLCPP_ERROR(this->get_logger(), "Failed to call GetRobotResources");
 48      }
 49      return false;
 50    }
 51
 52    auto response = future_result.get();
 53    resources_ = response->robot_resources;
 54    RCLCPP_INFO(this->get_logger(), "Found %zu resources", resources_.size());
 55    return true;
 56  }
 57
 58  void print_resource_menu() {
 59    if (resources_.empty()) {
 60      std::cout << "(no resources)\n";
 61      return;
 62    }
 63
 64    std::cout << "\n=== Available Robot Resources ===\n";
 65    for (size_t i = 0; i < resources_.size(); ++i) {
 66      const auto &ver = resources_[i].current_version;
 67      std::cout << "[" << i << "] "
 68                << (ver.name.empty() ? "<no name>" : ver.name) << "\n";
 69      std::cout << "     key:     " << resources_[i].resource_key << "\n";
 70      std::cout << "     version: " << ver.version << "\n";
 71    }
 72    std::cout << "=================================\n\n";
 73  }
 74
 75  int pick_resource() {
 76    if (resources_.empty()) {
 77      return -1;
 78    }
 79
 80    std::string line;
 81    while (true) {
 82      std::cout << "Select resource index [0-" << resources_.size() - 1
 83                << "] (q to quit): ";
 84      if (!std::getline(std::cin, line)) {
 85        return -1;
 86      }
 87      if (line == "q" || line == "quit" || line == "exit") {
 88        return -1;
 89      }
 90      try {
 91        int idx = std::stoi(line);
 92        if (idx >= 0 && idx < static_cast<int>(resources_.size())) {
 93          return idx;
 94        }
 95        std::cout << "Index out of range (0-" << resources_.size() - 1
 96                  << ").\n";
 97      } catch (...) {
 98        std::cout << "Please enter a number.\n";
 99      }
100    }
101  }
102
103  void play_resource(int idx) {
104    while (!play_resource_client_->wait_for_service(1s)) {
105      if (!rclcpp::ok()) {
106        RCLCPP_ERROR(this->get_logger(),
107                     "Interrupted while waiting for service.");
108        return;
109      }
110      RCLCPP_INFO(this->get_logger(), "Service not available, waiting...");
111    }
112
113    if (!rclcpp::ok()) {
114      return;
115    }
116
117    const auto &resource = resources_[idx];
118    auto request =
119        std::make_shared<aimdk_msgs::srv::ExecuteActionResource::Request>();
120
121    request->resource_key = resource.resource_key;
122    request->resource_version = resource.current_version.version;
123
124    if (request->resource_key.find("onnx") != std::string::npos) {
125      request->meta = R"({"resource_type": "BODY_MONTION"})";
126    } else {
127      request->meta = R"({"resource_type": "ARM_MONTION"})";
128    }
129
130    RCLCPP_INFO(this->get_logger(), "Sending request:");
131    RCLCPP_INFO(this->get_logger(), "  resource_key: %s",
132                request->resource_key.c_str());
133    RCLCPP_INFO(this->get_logger(), "  resource_version: %s",
134                request->resource_version.c_str());
135    RCLCPP_INFO(this->get_logger(), "  meta: %s", request->meta.c_str());
136
137    auto future = play_resource_client_->async_send_request(request);
138
139    if (rclcpp::spin_until_future_complete(this->get_node_base_interface(),
140                                           future) ==
141        rclcpp::FutureReturnCode::SUCCESS) {
142      handle_response(future.get());
143    } else {
144      if (rclcpp::ok()) {
145        RCLCPP_ERROR(this->get_logger(),
146                     "Failed to call ExecuteActionResource");
147      }
148    }
149  }
150
151private:
152  rclcpp::Client<aimdk_msgs::srv::GetRobotResources>::SharedPtr
153      get_resource_client_;
154  rclcpp::Client<aimdk_msgs::srv::ExecuteActionResource>::SharedPtr
155      play_resource_client_;
156  std::vector<aimdk_msgs::msg::RobotResource> resources_;
157
158  void handle_response(
159      const aimdk_msgs::srv::ExecuteActionResource::Response::SharedPtr
160          response) {
161    RCLCPP_INFO(this->get_logger(), "Service response received:");
162    RCLCPP_INFO(this->get_logger(), "Response Header:");
163    RCLCPP_INFO(this->get_logger(), "  stamp: %d.%09u",
164                response->header.header.stamp.sec,
165                response->header.header.stamp.nanosec);
166    RCLCPP_INFO(this->get_logger(), "  code: %ld",
167                response->header.header.code);
168    RCLCPP_INFO(this->get_logger(), "Status:");
169    RCLCPP_INFO(this->get_logger(), "  value: %d",
170                response->header.status.value);
171    RCLCPP_INFO(this->get_logger(), "  message: %s",
172                response->header.message.c_str());
173  }
174};
175
176int main(int argc, char **argv) {
177  rclcpp::init(argc, argv);
178  auto client_node = std::make_shared<RobotResourceClient>();
179
180  if (!client_node->get_robot_resources()) {
181    rclcpp::shutdown();
182    return 1;
183  }
184
185  client_node->print_resource_menu();
186  int chosen = client_node->pick_resource();
187  if (chosen < 0) {
188    RCLCPP_INFO(client_node->get_logger(), "No resource selected, exiting.");
189    rclcpp::shutdown();
190    return 0;
191  }
192
193  client_node->play_resource(chosen);
194  rclcpp::shutdown();
195  return 0;
196}

使用说明:

ros2 run examples play_linkcraft 
[INFO] [1772529153.517267476] [robot_resource_client]: Service call succeeded
[INFO] [1772529153.517416880] [robot_resource_client]: Robot Resources (0):
[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: {"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.28 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 <chrono>
  4#include <memory>
  5#include <rclcpp/rclcpp.hpp>
  6#include <signal.h>
  7#include <string>
  8
  9std::shared_ptr<rclcpp::Node> g_node = nullptr;
 10
 11void signal_handler(int signal) {
 12  if (g_node) {
 13    RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
 14                signal);
 15    g_node.reset();
 16  }
 17  rclcpp::shutdown();
 18  exit(signal);
 19}
 20
 21class PlayLightsClient : public rclcpp::Node {
 22public:
 23  PlayLightsClient() : Node("play_lights_client") {
 24    client_ = this->create_client<aimdk_msgs::srv::SetPmuLed>(
 25        "/aimdk_5Fmsgs/srv/SetPmuLed");
 26    RCLCPP_INFO(this->get_logger(), "✅ PlayLights client node started.");
 27
 28    // Wait for the service to become available
 29    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 30      RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
 31    }
 32    RCLCPP_INFO(this->get_logger(),
 33                "🟢 Service available, ready to send request.");
 34  }
 35
 36  bool send_request(uint8_t led_mode, uint8_t r, uint8_t g, uint8_t b,
 37                    int32_t priority = 6, bool reset_priority = false) {
 38    try {
 39      auto request = std::make_shared<aimdk_msgs::srv::SetPmuLed::Request>();
 40
 41      request->led_strip_mode = led_mode;
 42      request->r = r;
 43      request->g = g;
 44      request->b = b;
 45      request->priority = priority;
 46      request->reset_priority = reset_priority;
 47      request->trace_id = "play_lights";
 48
 49      RCLCPP_INFO(this->get_logger(),
 50                  "📨 Sending request to control led strip: mode=%hhu, "
 51                  "RGB=(%hhu, %hhu, %hhu), priority=%d, reset_priority=%s",
 52                  led_mode, r, g, b, priority,
 53                  reset_priority ? "true" : "false");
 54
 55      // LED strip is slow to response (up to ~5s)
 56      const std::chrono::milliseconds timeout(5000);
 57      for (int i = 0; i < 4; i++) {
 58        request->request.header.stamp = this->now();
 59        auto future = client_->async_send_request(request);
 60        auto retcode = rclcpp::spin_until_future_complete(
 61            this->shared_from_this(), future, timeout);
 62
 63        if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
 64          // retry as remote peer is NOT handled well by ROS
 65          RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
 66          continue;
 67        }
 68        // future.done
 69        auto response = future.get();
 70        if (response->status_code == 0) {
 71          RCLCPP_INFO(this->get_logger(),
 72                      "✅ LED strip command sent successfully.");
 73          return true;
 74        } else {
 75          RCLCPP_ERROR(this->get_logger(),
 76                       "❌ LED strip command failed with status: %d",
 77                       response->status_code);
 78          return false;
 79        }
 80      }
 81      RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
 82      return false;
 83    } catch (const std::exception &e) {
 84      RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
 85      return false;
 86    }
 87  }
 88
 89private:
 90  rclcpp::Client<aimdk_msgs::srv::SetPmuLed>::SharedPtr client_;
 91};
 92
 93int main(int argc, char **argv) {
 94  try {
 95    rclcpp::init(argc, argv);
 96    signal(SIGINT, signal_handler);
 97    signal(SIGTERM, signal_handler);
 98
 99    g_node = std::make_shared<PlayLightsClient>();
100    auto client_node = std::dynamic_pointer_cast<PlayLightsClient>(g_node);
101
102    int led_mode = 0;             // LED Strip Mode
103    int r = 255, g = 0, b = 0;    // RGB values
104    int priority = 6;             // Priority
105    int reset_priority_input = 0; // Reset priority flag
106
107    std::cout << "=== LED Strip Control Example ===" << std::endl;
108    std::cout << "Select LED strip mode:" << std::endl;
109    std::cout << "0 - Steady On" << std::endl;
110    std::cout << "1 - Breathing (4s period, sinusoidal brightness)"
111              << std::endl;
112    std::cout << "2 - Blinking (1s period, 0.5s on, 0.5s off)" << std::endl;
113    std::cout << "3 - Flow (2s period, lights turn on left to right)"
114              << std::endl;
115    std::cout << "Enter mode (0-3): ";
116    std::cin >> led_mode;
117
118    std::cout << "\nSet RGB color values (0-255):" << std::endl;
119    std::cout << "Red component (R): ";
120    std::cin >> r;
121    std::cout << "Green component (G): ";
122    std::cin >> g;
123    std::cout << "Blue component (B): ";
124    std::cin >> b;
125
126    std::cout << "\nSet priority (higher value = higher priority, default=6): ";
127    std::cin >> priority;
128
129    std::cout << "Reset priority after command? (1=yes, 0=no, default=0): ";
130    std::cin >> reset_priority_input;
131
132    // clamp mode to range 0-3
133    led_mode = std::max(0, std::min(3, led_mode));
134    // clamp r/g/b to range 0-255
135    r = std::max(0, std::min(255, r));
136    g = std::max(0, std::min(255, g));
137    b = std::max(0, std::min(255, b));
138    // clamp priority to non-negative
139    priority = std::max(0, priority);
140
141    if (client_node) {
142      client_node->send_request(led_mode, r, g, b, priority,
143                                reset_priority_input != 0);
144    }
145
146    g_node.reset();
147    rclcpp::shutdown();
148
149    return 0;
150  } catch (const std::exception &e) {
151    RCLCPP_ERROR(rclcpp::get_logger("main"),
152                 "Program terminated with exception: %s", e.what());
153    return 1;
154  }
155}

使用说明

# 构建
colcon build --packages-select examples

# 运行
ros2 run examples play_lights

输出示例

=== LED Strip Control Example ===
Select LED strip mode:
0 - Steady On
1 - Breathing (4s period, sinusoidal brightness)
2 - Blinking (1s period, 0.5s on, 0.5s off)
3 - Flow (2s period, lights turn on left to right)
Enter mode (0-3): 1

Set RGB color values (0-255):
Red component (R): 255
Green component (G): 0
Blue component (B): 0

Set priority (higher value = higher priority, default=6): 6
Reset priority after command? (1=yes, 0=no, default=0): 0

[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.29 获取当前麦克风

  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) {
 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.30 切换麦克风

  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) {
 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 - 内置麦