6.2 C++接口使用示例

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

构建 & 运行说明

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

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

📝 代码说明

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

6.2.1 获取机器人模式

通过调用GetMcAction服务获取机器人当前的运行模式,包括模式ID、描述和状态信息。

运动模式定义

  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    rclcpp::shutdown();
 18  }
 19}
 20
 21class GetMcActionClient : public rclcpp::Node {
 22public:
 23  GetMcActionClient() : Node("get_mc_action_client") {
 24    // Set service call timeout
 25    const std::chrono::seconds timeout(8);
 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(), "Get MC Action Client created");
 30
 31    // Add timeout handling
 32    auto start_time = std::chrono::steady_clock::now();
 33    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 34      if (std::chrono::steady_clock::now() - start_time > timeout) {
 35        RCLCPP_ERROR(this->get_logger(), "Service wait timed out");
 36        rclcpp::shutdown();
 37        return;
 38      }
 39      RCLCPP_INFO(this->get_logger(), "Service unavailable, waiting...");
 40    }
 41  }
 42
 43  void send_request() {
 44    try {
 45      auto request = std::make_shared<aimdk_msgs::srv::GetMcAction::Request>();
 46      request->request = aimdk_msgs::msg::CommonRequest();
 47
 48      RCLCPP_INFO(this->get_logger(), "Sending request to get robot mode");
 49
 50      // Set a service call timeout
 51      const std::chrono::milliseconds timeout(250);
 52      for (int i = 0; i < 8; i++) {
 53        auto future = client_->async_send_request(request);
 54        auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
 55                                                          future, timeout);
 56        if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
 57          // retry as remote peer: mc module can be under heavy load
 58          RCLCPP_INFO(this->get_logger(), "trying to send request... [%d]", i);
 59          continue;
 60        }
 61        // future.done
 62        auto response = future.get();
 63        RCLCPP_INFO(this->get_logger(), "Current robot mode: %d",
 64                    response->info.current_action.value);
 65        RCLCPP_INFO(this->get_logger(), "Mode description: %s",
 66                    response->info.action_desc.c_str());
 67        RCLCPP_INFO(this->get_logger(), "Mode status: %d",
 68                    response->info.status.value);
 69        return;
 70      }
 71      RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out");
 72    } catch (const std::exception &e) {
 73      RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
 74    }
 75  }
 76
 77private:
 78  rclcpp::Client<aimdk_msgs::srv::GetMcAction>::SharedPtr client_;
 79};
 80
 81int main(int argc, char *argv[]) {
 82  try {
 83    rclcpp::init(argc, argv);
 84
 85    // Set up signal handlers
 86    signal(SIGINT, signal_handler);
 87    signal(SIGTERM, signal_handler);
 88
 89    // Create node
 90    g_node = std::make_shared<GetMcActionClient>();
 91    auto client = std::dynamic_pointer_cast<GetMcActionClient>(g_node);
 92    std::cout << "Robot modes: (1: default, 100: position-control stand, 200: "
 93                 "stable stand, 300: walk):"
 94              << std::endl;
 95    if (client) {
 96      client->send_request();
 97    }
 98
 99    // Clean up resources
100    g_node.reset();
101    rclcpp::shutdown();
102
103    return 0;
104  } catch (const std::exception &e) {
105    RCLCPP_ERROR(rclcpp::get_logger("main"),
106                 "Program exited with exception: %s", e.what());
107    return 1;
108  }
109}

使用说明

ros2 run examples get_mc_action

输出示例

[INFO] [get_mc_action_client]: 当前机器人模式: 100
[INFO] [get_mc_action_client]: 模式描述: 站姿预备(位控站立)模式
[INFO] [get_mc_action_client]: 模式状态: 1

接口参考

  • 服务:/aimdk_5Fmsgs/srv/GetMcAction

  • 消息:aimdk_msgs/srv/GetMcAction

6.2.2 设置机器人模式

该示例中用到了SetMcAction服务,运行节点程序后终端输入模式对应的字段值,机器人会立即进入相应的运动模式
切入稳定站立(STAND_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 <memory>
 10#include <signal.h>
 11
 12// Global variable used for signal handling
 13std::shared_ptr<rclcpp::Node> g_node = nullptr;
 14
 15// Signal handler function
 16void signal_handler(int signal) {
 17  if (g_node) {
 18    RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
 19                signal);
 20    rclcpp::shutdown();
 21  }
 22}
 23
 24class SetMcActionClient : public rclcpp::Node {
 25public:
 26  SetMcActionClient() : Node("set_mc_action_client") {
 27    // Set service call timeout
 28    const std::chrono::seconds timeout(8);
 29
 30    client_ = this->create_client<aimdk_msgs::srv::SetMcAction>(
 31        "/aimdk_5Fmsgs/srv/SetMcAction");
 32    RCLCPP_INFO(this->get_logger(), "Set MC Action Client created");
 33
 34    // Add timeout handling
 35    auto start_time = std::chrono::steady_clock::now();
 36    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 37      if (std::chrono::steady_clock::now() - start_time > timeout) {
 38        RCLCPP_ERROR(this->get_logger(), "Waiting for service timed out");
 39        rclcpp::shutdown();
 40        return;
 41      }
 42      RCLCPP_INFO(this->get_logger(), "Service not available, waiting...");
 43    }
 44  }
 45
 46  bool send_request(int action_value) {
 47    try {
 48      auto request = std::make_shared<aimdk_msgs::srv::SetMcAction::Request>();
 49      request->header = aimdk_msgs::msg::RequestHeader();
 50
 51      // Set robot mode
 52      aimdk_msgs::msg::McActionCommand command;
 53      aimdk_msgs::msg::McAction action;
 54      action.value = action_value;
 55      command.action = action;
 56      command.action_desc =
 57          "Set robot mode to: " + std::to_string(action_value);
 58      request->command = command;
 59
 60      RCLCPP_INFO(this->get_logger(), "Sending request to set robot mode: %d",
 61                  action_value);
 62
 63      // Set Service Call Timeout
 64      const std::chrono::milliseconds timeout(250);
 65      for (int i = 0; i < 8; i++) {
 66        auto future = client_->async_send_request(request);
 67        auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
 68                                                          future, timeout);
 69        if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
 70          // retry as remote peer: mc module can be under heavy load
 71          RCLCPP_INFO(this->get_logger(), "trying to send request... [%d]", i);
 72          continue;
 73        }
 74        // future.done
 75        auto response = future.get();
 76        if (response->response.status.value ==
 77            aimdk_msgs::msg::CommonState::SUCCESS) {
 78          RCLCPP_INFO(this->get_logger(), "Set robot mode succeeded");
 79          return true;
 80        } else {
 81          RCLCPP_ERROR(this->get_logger(), "Set robot mode failed: %s",
 82                       response->response.message.c_str());
 83          return false;
 84        }
 85      }
 86      RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out");
 87      return false;
 88    } catch (const std::exception &e) {
 89      RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
 90      return false;
 91    }
 92  }
 93
 94private:
 95  rclcpp::Client<aimdk_msgs::srv::SetMcAction>::SharedPtr client_;
 96};
 97
 98int main(int argc, char *argv[]) {
 99  try {
100    rclcpp::init(argc, argv);
101
102    // Set up signal handlers
103    signal(SIGINT, signal_handler);
104    signal(SIGTERM, signal_handler);
105
106    // Create node
107    g_node = std::make_shared<SetMcActionClient>();
108    auto client = std::dynamic_pointer_cast<SetMcActionClient>(g_node);
109
110    if (client) {
111      int action_value = 100; // JOINT_DEFAULT Mode
112
113      // Prefer command-line argument; otherwise prompt user
114      if (argc > 1) {
115        action_value = std::atoi(argv[1]);
116        RCLCPP_INFO(g_node->get_logger(), "Using command-line argument: %d",
117                    action_value);
118      } else {
119        std::cout << "Enter robot mode (1: default, 100: position-control "
120                     "stand, 200: "
121                     "stable stand, 300: walk): ";
122        std::cin >> action_value;
123      }
124
125      if (!client->send_request(action_value)) {
126        RCLCPP_ERROR(g_node->get_logger(), "Failed to set robot mode");
127        return 1;
128      }
129    }
130
131    // Clean up resources
132    g_node.reset();
133    rclcpp::shutdown();
134
135    return 0;
136  } catch (const std::exception &e) {
137    RCLCPP_ERROR(rclcpp::get_logger("main"),
138                 "Program exited with exception: %s", e.what());
139    return 1;
140  }
141}

使用说明

# 使用命令行参数设置模式(推荐)
ros2 run examples set_mc_action 100  # 站姿预备(位控站立)模式
ros2 run examples set_mc_action 300  # 走跑模式

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

输出示例

[INFO] [set_mc_action_client]: 设置机器人模式成功

注意事项

  • 切入stand_default模式前,确保机器人脚已经着地

  • 模式切换可能需要几秒钟时间完成

接口参考

  • 服务:/aimdk_5Fmsgs/srv/SetMcAction

  • 消息:aimdk_msgs/srv/SetMcAction

6.2.3 设置机器人动作

该示例中用到了preset_motion_client,启动节点程序后,输入相应的字段值可实现左手(右手)的握手(举手、挥手、飞吻)动作。

可供调用的参数见预设动作表

  1#include "aimdk_msgs/msg/common_response.hpp"
  2#include "aimdk_msgs/msg/common_state.hpp"
  3#include "aimdk_msgs/msg/common_task_response.hpp"
  4#include "aimdk_msgs/msg/mc_control_area.hpp"
  5#include "aimdk_msgs/msg/mc_preset_motion.hpp"
  6#include "aimdk_msgs/msg/request_header.hpp"
  7#include "aimdk_msgs/srv/set_mc_preset_motion.hpp"
  8#include "rclcpp/rclcpp.hpp"
  9#include <chrono>
 10#include <memory>
 11#include <signal.h>
 12
 13std::shared_ptr<rclcpp::Node> g_node = nullptr;
 14
 15void signal_handler(int signal) {
 16  if (g_node) {
 17    RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
 18                signal);
 19    rclcpp::shutdown();
 20  }
 21}
 22
 23class PresetMotionClient : public rclcpp::Node {
 24public:
 25  PresetMotionClient() : Node("preset_motion_client") {
 26    const std::chrono::seconds timeout(8);
 27
 28    client_ = this->create_client<aimdk_msgs::srv::SetMcPresetMotion>(
 29        "/aimdk_5Fmsgs/srv/SetMcPresetMotion");
 30
 31    RCLCPP_INFO(this->get_logger(), "Preset motion service client created");
 32
 33    // Add timeout handling
 34    auto start_time = std::chrono::steady_clock::now();
 35    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 36      if (std::chrono::steady_clock::now() - start_time > timeout) {
 37        RCLCPP_ERROR(this->get_logger(), "Waiting for service timed out");
 38        rclcpp::shutdown();
 39        return;
 40      }
 41      RCLCPP_INFO(this->get_logger(), "Waiting for service to start...");
 42    }
 43  }
 44
 45  bool send_motion_request(int area_id, int motion_id) {
 46    try {
 47      auto request =
 48          std::make_shared<aimdk_msgs::srv::SetMcPresetMotion::Request>();
 49      request->header = aimdk_msgs::msg::RequestHeader();
 50
 51      aimdk_msgs::msg::McPresetMotion motion;
 52      aimdk_msgs::msg::McControlArea area;
 53
 54      motion.value = motion_id; // Preset motion ID
 55      area.value = area_id;     // Control area ID
 56      request->motion = motion;
 57      request->area = area;
 58      request->interrupt = false; // Not interrupt current motion
 59
 60      RCLCPP_INFO(this->get_logger(),
 61                  "Arm:(ID=%d); sending preset motion request: (ID=%d)",
 62                  area_id, motion_id);
 63
 64      const std::chrono::milliseconds timeout(250);
 65
 66      for (int i = 0; i < 8; i++) {
 67        request->header.stamp = rclcpp::Clock().now();
 68        auto future = client_->async_send_request(request);
 69        auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
 70                                                          future, timeout);
 71        if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
 72          // retry as remote peer: mc module can be under heavy load
 73          RCLCPP_INFO(this->get_logger(),
 74                      "trying to sned preset motion request... [%d]", i);
 75          continue;
 76        }
 77        // future.done
 78        auto response = future.get();
 79        if (response->response.state.value ==
 80            aimdk_msgs::msg::CommonState::SUCCESS) {
 81          RCLCPP_INFO(this->get_logger(), "Motion executed successfully: %lu",
 82                      response->response.task_id);
 83          return true;
 84        } else if (response->response.state.value ==
 85                   aimdk_msgs::msg::CommonState::RUNNING) {
 86          RCLCPP_INFO(this->get_logger(), "Motion is running: %lu",
 87                      response->response.task_id);
 88          return true;
 89        } else {
 90          RCLCPP_WARN(this->get_logger(), "Motion execution failed: %lu",
 91                      response->response.task_id);
 92          return false;
 93        }
 94      }
 95      RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out");
 96      return false;
 97    } catch (const std::exception &e) {
 98      RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
 99      return false;
100    }
101  }
102
103private:
104  rclcpp::Client<aimdk_msgs::srv::SetMcPresetMotion>::SharedPtr client_;
105};
106
107int main(int argc, char *argv[]) {
108  rclcpp::init(argc, argv);
109  signal(SIGINT, signal_handler);
110  signal(SIGTERM, signal_handler);
111
112  g_node = std::make_shared<PresetMotionClient>();
113  // Cast g_node (std::shared_ptr<rclcpp::Node>) to a derived
114  // PresetMotionClient pointer (std::shared_ptr<PresetMotionClient>)
115  auto client = std::dynamic_pointer_cast<PresetMotionClient>(g_node);
116
117  int HAND_area = 1;
118  int HAND_motion = 1003;
119  std::cout << "Define the arm area for the preset motion: left=1, right=2"
120            << std::endl;
121  std::cout << "Enter arm area ID (1-left, 2-right): ";
122  std::cin >> HAND_area;
123  std::cout << "Define preset motions: raise hand=1001, wave=1002, "
124               "handshake=1003, air-kiss=1004"
125            << std::endl;
126  std::cout << "Enter preset motion ID (1001-raise, 1002-wave, 1003-handshake, "
127               "1004-airkiss): ";
128  std::cin >> HAND_motion;
129  if (client) {
130    if (!client->send_motion_request(HAND_area, HAND_motion)) {
131      RCLCPP_ERROR(g_node->get_logger(), "Failed to send motion request");
132      return 1;
133    }
134  }
135  g_node.reset();
136  rclcpp::shutdown();
137  return 0;
138}

6.2.4 夹爪控制

该示例中用到了hand_control,通过往话题/aima/hal/joint/hand/command发布消息来控制夹爪的运动

注意

注意⚠️ :运行本示例前需要在机器人运控计算单元(PC1)使用aima em stop-app mc关闭原生运控模块,获取机器人控制权。注意确保机器人安全

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

6.2.5 灵巧手控制 (升级中暂不开放)

该示例中用到了omnihand_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
  9/**
 10 * @brief Omnihand control node
 11 */
 12class OmnihandControl : public rclcpp::Node {
 13public:
 14  OmnihandControl()
 15      : Node("omnihand_control"), position_pairs_({
 16                                      {1.0, 1.0}, // Hands fully open
 17                                      {0.0, 0.0}, // Hands fully closed
 18                                      {0.5, 0.5}, // Hands half open
 19                                      {0.3, 0.7}, // Left closed right open
 20                                      {0.8, 0.2}  // Left open right closed
 21                                  }),
 22        current_index_(0) {
 23    publisher_ = this->create_publisher<aimdk_msgs::msg::HandCommandArray>(
 24        "/aima/hal/joint/hand/command", 10);
 25
 26    timer_ = this->create_wall_timer(
 27        std::chrono::milliseconds(20), // 50Hz
 28        std::bind(&OmnihandControl::publish_hand_commands, this));
 29
 30    last_switch_time_ = now();
 31    RCLCPP_INFO(this->get_logger(), "Omnihand control node has been started!");
 32  }
 33
 34  /**
 35   * @brief Publish hand commands
 36   */
 37  void publish_hand_commands() {
 38    // 1. Switches parameter set every 5 seconds
 39    auto now_time = this->now();
 40    if ((now_time - last_switch_time_).seconds() >= 5.0) {
 41      current_index_ = (current_index_ + 1) % position_pairs_.size();
 42      last_switch_time_ = now_time;
 43      RCLCPP_INFO(
 44          this->get_logger(),
 45          "Switched to parameter set %zu, left hand = %.2f, right hand = %.2f",
 46          current_index_, position_pairs_[current_index_].first,
 47          position_pairs_[current_index_].second);
 48    }
 49
 50    auto msg = std::make_unique<aimdk_msgs::msg::HandCommandArray>();
 51    msg->header = aimdk_msgs::msg::MessageHeader();
 52
 53    float left_position = position_pairs_[current_index_].first;
 54    float right_position = position_pairs_[current_index_].second;
 55
 56    // Left hand 10 joints
 57    for (int i = 0; i < 10; ++i) {
 58      aimdk_msgs::msg::HandCommand left_hands;
 59      left_hands.name = "left_hand_joint" + std::to_string(i);
 60      left_hands.position = left_position;
 61      left_hands.velocity = 1.0;
 62      left_hands.acceleration = 1.0;
 63      left_hands.deceleration = 1.0;
 64      left_hands.effort = 1.0;
 65      msg->left_hands.push_back(left_hands);
 66    }
 67    // Right hand 10 joints
 68    for (int i = 0; i < 10; ++i) {
 69      aimdk_msgs::msg::HandCommand right_hands;
 70      right_hands.name = "right_hand_joint" + std::to_string(i);
 71      right_hands.position = right_position;
 72      right_hands.velocity = 1.0;
 73      right_hands.acceleration = 1.0;
 74      right_hands.deceleration = 1.0;
 75      right_hands.effort = 1.0;
 76      msg->right_hands.push_back(right_hands);
 77    }
 78
 79    // Type 1: Dexterous hand mode
 80    msg->left_hand_type.value = 1;
 81    msg->right_hand_type.value = 1;
 82
 83    publisher_->publish(std::move(msg));
 84  }
 85
 86private:
 87  // Member variables
 88  rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
 89  rclcpp::TimerBase::SharedPtr timer_;
 90
 91  std::vector<std::pair<float, float>> position_pairs_;
 92  size_t current_index_;
 93  rclcpp::Time last_switch_time_;
 94};
 95
 96/**
 97 * @brief Main function
 98 */
 99// Initializes ROS, creates node, and starts spinning
100int main(int argc, char *argv[]) {
101  rclcpp::init(argc, argv);
102  auto omnihand_control_node = std::make_shared<OmnihandControl>();
103  rclcpp::spin(omnihand_control_node);
104  rclcpp::shutdown();
105  return 0;
106}

6.2.6 注册二开输入源

对于v0.7之后的版本,在实现对MC的控制前,需要先注册输入源。该示例中通过/aimdk_5Fmsgs/srv/SetMcInputSource服务来注册二开的输入源,让MC感知到,只有注册过输入源后才能实现机器人的速度控制

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

6.2.7 获取当前输入源

该示例中用到了GetCurrentInputSource服务,用于获取当前已注册的输入源信息,包括输入源名称、优先级和超时时间等信息。

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

使用说明

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

输出示例

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

注意事项

  • 确保GetCurrentInputSource服务正常运行

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

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

6.2.8 机器人走跑控制

该示例中用到了mc_locomotion_velocity,以下示例通过发布/aima/mc/locomotion/velocity话题控制机器人的行走,对于v0.7之后的版本,实现速度控制前需要注册输入源(该示例已实现注册输入源),具体注册步骤可参考代码。

  1#include "aimdk_msgs/msg/mc_locomotion_velocity.hpp"
  2#include "aimdk_msgs/msg/common_request.hpp"
  3#include "aimdk_msgs/msg/common_response.hpp"
  4#include "aimdk_msgs/msg/common_state.hpp"
  5#include "aimdk_msgs/msg/common_task_response.hpp"
  6#include "aimdk_msgs/msg/mc_input_action.hpp"
  7#include "aimdk_msgs/msg/message_header.hpp"
  8#include "aimdk_msgs/srv/set_mc_input_source.hpp"
  9
 10#include "rclcpp/rclcpp.hpp"
 11#include <chrono>
 12#include <memory>
 13#include <signal.h>
 14#include <thread>
 15
 16class DirectVelocityControl : public rclcpp::Node {
 17public:
 18  DirectVelocityControl() : Node("direct_velocity_control") {
 19    // Create publisher
 20    publisher_ = this->create_publisher<aimdk_msgs::msg::McLocomotionVelocity>(
 21        "/aima/mc/locomotion/velocity", 10);
 22    // Create service client
 23    client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
 24        "/aimdk_5Fmsgs/srv/SetMcInputSource");
 25
 26    // Maximum speed limits
 27    max_forward_speed_ = 1.0; // m/s
 28    max_lateral_speed_ = 0.5; // 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: mc module can be under heavy load
 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}
184
185int main(int argc, char *argv[]) {
186  rclcpp::init(argc, argv);
187  auto node = std::make_shared<DirectVelocityControl>();
188  global_node = node;
189  signal(SIGINT, signal_handler);
190  signal(SIGTERM, signal_handler);
191
192  if (!node->register_input_source()) {
193    RCLCPP_ERROR(node->get_logger(),
194                 "Input source registration failed, exiting");
195    rclcpp::shutdown();
196    return 1;
197  }
198
199  // get and check control values
200  // notice that mc has thresholds to start movement
201  double forward, lateral, angular;
202  std::cout << "Enter forward speed 0 or ±(0.2 ~ 1.0) m/s: ";
203  std::cin >> forward;
204  if (!node->set_forward(forward)) {
205    return 2;
206  }
207  std::cout << "Enter lateral speed 0 or ±(0.2 ~ 0.5) m/s: ";
208  std::cin >> lateral;
209  if (!node->set_lateral(lateral)) {
210    return 2;
211  }
212  std::cout << "Enter angular speed 0 or ±(0.1 ~ 1.0) rad/s: ";
213  std::cin >> angular;
214  if (!node->set_angular(angular)) {
215    return 2;
216  }
217
218  RCLCPP_INFO(node->get_logger(), "Setting velocity; moving for 5 seconds");
219
220  node->start_publish();
221
222  auto start_time = node->now();
223  while ((node->now() - start_time).seconds() < 5.0) {
224    rclcpp::spin_some(node);
225    std::this_thread::sleep_for(std::chrono::milliseconds(1));
226  }
227
228  node->clear_velocity();
229  RCLCPP_INFO(node->get_logger(), "5 seconds elapsed; robot stopped");
230
231  rclcpp::spin(node);
232  rclcpp::shutdown();
233  return 0;
234}

6.2.9 关节电机控制

本示例展示了如何使用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.4871, 2.4871, 40.0, 4.0}, // Left hip pitch
 58         {"left_hip_roll_joint", -0.12217, 2.9059, 40.0, 4.0}, // Left hip roll
 59         {"left_hip_yaw_joint", -1.6842, 3.4296, 30.0, 3.0},   // Left hip yaw
 60         {"left_knee_joint", 0.026179, 2.1206, 80.0, 8.0},     // Left knee
 61         {"left_ankle_pitch_joint", -0.80285, 0.45378, 40.0,
 62          4.0}, // Left ankle pitch
 63         {"left_ankle_roll_joint", -0.2618, 0.2618, 20.0,
 64          2.0}, // Left ankle roll
 65                // Right leg joint configuration
 66         {"right_hip_pitch_joint", -2.4871, 2.4871, 40.0,
 67          4.0}, // Right hip pitch
 68         {"right_hip_roll_joint", -2.9059, 0.12217, 40.0,
 69          4.0},                                               // Right hip roll
 70         {"right_hip_yaw_joint", -3.4296, 1.6842, 30.0, 3.0}, // Right hip yaw
 71         {"right_knee_joint", 0.026179, 2.1206, 80.0, 8.0},   // Right knee
 72         {"right_ankle_pitch_joint", -0.80285, 0.45378, 40.0,
 73          4.0}, // Right ankle pitch
 74         {"right_ankle_roll_joint", -0.2618, 0.2618, 20.0,
 75          2.0}, // Right ankle roll
 76     }},
 77
 78    {JointArea::WAIST,
 79     {
 80         // Waist joint configuration
 81         {"waist_yaw_joint", -3.4296, 2.3824, 20.0, 4.0},     // Waist yaw
 82         {"waist_pitch_joint", -0.17453, 0.17453, 20.0, 4.0}, // Waist pitch
 83         {"waist_roll_joint", -0.48869, 0.48869, 20.0, 4.0},  // Waist roll
 84     }},
 85    {JointArea::ARM,
 86     {
 87         // Left arm joint configuration
 88         {"left_shoulder_pitch_joint", -2.5569, 2.5569, 20.0,
 89          2.0}, // Left shoulder pitch
 90         {"left_shoulder_roll_joint", -0.06108, 3.3598, 20.0,
 91          2.0}, // Left shoulder roll
 92         {"left_shoulder_yaw_joint", -2.5569, 2.5569, 20.0,
 93          2.0}, // Left shoulder yaw
 94         {"left_elbow_pitch_joint", -2.4435, 0.0, 20.0,
 95          2.0}, // Left elbow pitch
 96         {"left_elbow_roll_joint", -2.5569, 2.5569, 20.0,
 97          2.0}, // Left elbow roll
 98         {"left_wrist_pitch_joint", -0.5585, 0.5585, 20.0,
 99          2.0}, // Left wrist pitch
100         {"left_wrist_yaw_joint", -1.5446, 1.5446, 20.0,
101          2.0}, // Left wrist yaw
102                // Right arm joint configuration
103         {"right_shoulder_pitch_joint", -2.5569, 2.5569, 20.0,
104          2.0}, // Right shoulder pitch
105         {"right_shoulder_roll_joint", -3.3598, 0.06108, 20.0,
106          2.0}, // Right shoulder roll
107         {"right_shoulder_yaw_joint", -2.5569, 2.5569, 20.0,
108          2.0}, // Right shoulder yaw
109         {"right_elbow_pitch_joint", 0.0, 2.4435, 20.0,
110          2.0}, // Right elbow pitch
111         {"right_elbow_roll_joint", -2.5569, 2.5569, 20.0,
112          2.0}, // Right elbow roll
113         {"right_wrist_pitch_joint", -0.5585, 0.5585, 20.0,
114          2.0}, // Right wrist pitch
115         {"right_wrist_yaw_joint", -1.5446, 1.5446, 20.0,
116          2.0}, // Right wrist yaw
117     }},
118    {JointArea::HEAD,
119     {
120         // Head joint configuration
121         {"head_pitch_joint", -0.61087, 0.61087, 20.0, 2.0}, // Head pitch
122         {"head_yaw_joint", -0.61087, 0.61087, 20.0, 2.0},   // Head yaw
123     }},
124};
125
126/**
127 * @brief Joint controller node class
128 * @tparam DOFs Degrees of freedom
129 * @tparam Area Joint area
130 */
131template <int DOFs, JointArea Area>
132class JointControllerNode : public rclcpp::Node {
133public:
134  /**
135   * @brief Constructor
136   * @param node_name Node name
137   * @param sub_topic Subscription topic name
138   * @param pub_topic Publication topic name
139   * @param qos QoS configuration
140   */
141  JointControllerNode(std::string node_name, std::string sub_topic,
142                      std::string pub_topic,
143                      rclcpp::QoS qos = rclcpp::SensorDataQoS())
144      : Node(node_name), ruckig(0.002) {
145    joint_info_ = robot_model[Area];
146    if (joint_info_.size() != DOFs) {
147      RCLCPP_ERROR(this->get_logger(), "Joint count mismatch.");
148      exit(1);
149    }
150
151    // Set motion constraints for Ruckig trajectory planner
152    for (int i = 0; i < DOFs; ++i) {
153      input.max_velocity[i] = 1.0;     // Max velocity limit
154      input.max_acceleration[i] = 1.0; // Max acceleration limit
155      input.max_jerk[i] = 25.0; // Max jerk (change of acceleration) limit
156    }
157
158    // Create joint state subscriber
159    sub_ = this->create_subscription<aimdk_msgs::msg::JointStateArray>(
160        sub_topic, qos,
161        std::bind(&JointControllerNode::JointStateCallback, this,
162                  std::placeholders::_1));
163
164    // Create joint command publisher
165    pub_ = this->create_publisher<aimdk_msgs::msg::JointCommandArray>(pub_topic,
166                                                                      qos);
167  }
168
169private:
170  // Ruckig trajectory planner variables
171  ruckig::Ruckig<DOFs> ruckig;          // Trajectory planner instance
172  ruckig::InputParameter<DOFs> input;   // Input parameters
173  ruckig::OutputParameter<DOFs> output; // Output parameters
174  bool ruckig_initialized_ = false;   // Trajectory planner initialization flag
175  std::vector<JointInfo> joint_info_; // Joint information list
176
177  // ROS communication variables
178  rclcpp::Subscription<aimdk_msgs::msg::JointStateArray>::SharedPtr
179      sub_; // State subscriber
180  rclcpp::Publisher<aimdk_msgs::msg::JointCommandArray>::SharedPtr
181      pub_; // Command publisher
182
183  /**
184   * @brief Joint state callback function
185   * @param msg Joint state message
186   */
187  void
188  JointStateCallback(const aimdk_msgs::msg::JointStateArray::SharedPtr msg) {
189    // Initialize trajectory planner on first state reception
190    if (!ruckig_initialized_) {
191      for (int i = 0; i < DOFs; ++i) {
192        input.current_position[i] = msg->joints[i].position;
193        input.current_velocity[i] = msg->joints[i].velocity;
194        input.current_acceleration[i] = 0.0;
195      }
196      ruckig_initialized_ = true;
197      RCLCPP_INFO(this->get_logger(),
198                  "Ruckig trajectory planner initialization complete");
199    }
200  }
201
202public:
203  /**
204   * @brief Set target joint position
205   * @param joint_name Joint name
206   * @param target_position Target position
207   * @return Whether the target position was successfully set
208   */
209  bool SetTargetPosition(std::string joint_name, double target_position) {
210    if (!ruckig_initialized_) {
211      RCLCPP_WARN(this->get_logger(),
212                  "Ruckig trajectory planner not initialized");
213      return false;
214    }
215
216    // Find target joint and set its position
217    int target_joint = -1;
218    for (int i = 0; i < DOFs; ++i) {
219      if (joint_info_[i].name == joint_name) {
220        // Check if target position is within limits
221        if (target_position < joint_info_[i].lower_limit ||
222            target_position > joint_info_[i].upper_limit) {
223          RCLCPP_ERROR(
224              this->get_logger(),
225              "Target position %.3f exceeds limit for joint %s [%.3f, %.3f]",
226              target_position, joint_name.c_str(), joint_info_[i].lower_limit,
227              joint_info_[i].upper_limit);
228          return false;
229        }
230        input.target_position[i] = target_position;
231        input.target_velocity[i] = 0.0;
232        input.target_acceleration[i] = 0.0;
233        target_joint = i;
234      } else {
235        input.target_position[i] = input.current_position[i];
236        input.target_velocity[i] = 0.0;
237        input.target_acceleration[i] = 0.0;
238      }
239    }
240
241    if (target_joint == -1) {
242      RCLCPP_ERROR(this->get_logger(), "Joint %s not found",
243                   joint_name.c_str());
244      return false;
245    }
246
247    // Perform trajectory planning and send command using Ruckig
248    const double tolerance = 1e-6;
249    while (g_running && rclcpp::ok() && !g_emergency_stop) {
250      auto result = ruckig.update(input, output);
251      if (result != ruckig::Result::Working &&
252          result != ruckig::Result::Finished) {
253        RCLCPP_WARN(this->get_logger(), "Trajectory planning failed");
254        break;
255      }
256
257      // Update current state
258      for (int i = 0; i < DOFs; ++i) {
259        input.current_position[i] = output.new_position[i];
260        input.current_velocity[i] = output.new_velocity[i];
261        input.current_acceleration[i] = output.new_acceleration[i];
262      }
263
264      // Check if target position is reached
265      if (std::abs(output.new_position[target_joint] - target_position) <
266          tolerance) {
267        RCLCPP_INFO(this->get_logger(), "Joint %s reached target position",
268                    joint_name.c_str());
269        break;
270      }
271
272      // Create and send joint command
273      aimdk_msgs::msg::JointCommandArray cmd;
274      cmd.joints.resize(DOFs);
275      for (int i = 0; i < DOFs; ++i) {
276        auto &joint = joint_info_[i];
277        cmd.joints[i].name = joint.name;
278        cmd.joints[i].position = output.new_position[i];
279        cmd.joints[i].velocity = output.new_velocity[i];
280        cmd.joints[i].stiffness = joint.kp;
281        cmd.joints[i].damping = joint.kd;
282      }
283      pub_->publish(cmd);
284
285      // Short delay to avoid excessive CPU usage
286      std::this_thread::sleep_for(std::chrono::milliseconds(2));
287    }
288
289    return true;
290  }
291
292  /**
293   * @brief Safely stop all joints
294   */
295  void safe_stop() {
296    if (!ruckig_initialized_) {
297      RCLCPP_WARN(this->get_logger(), "Ruckig trajectory planner not "
298                                      "initialized, cannot perform safe stop");
299      return;
300    }
301
302    RCLCPP_INFO(this->get_logger(), "Performing safe stop...");
303
304    // Set all joint target positions to current positions
305    for (int i = 0; i < DOFs; ++i) {
306      input.target_position[i] = input.current_position[i];
307      input.target_velocity[i] = 0.0;
308      input.target_acceleration[i] = 0.0;
309    }
310
311    // Send final command to ensure joints stop
312    aimdk_msgs::msg::JointCommandArray cmd;
313    cmd.joints.resize(DOFs);
314    for (int i = 0; i < DOFs; ++i) {
315      auto &joint = joint_info_[i];
316      cmd.joints[i].name = joint.name;
317      cmd.joints[i].position = input.current_position[i];
318      cmd.joints[i].velocity = 0.0;
319      cmd.joints[i].stiffness = joint.kp;
320      cmd.joints[i].damping = joint.kd;
321    }
322    pub_->publish(cmd);
323
324    RCLCPP_INFO(this->get_logger(), "Safe stop complete");
325  }
326
327  /**
328   * @brief Emergency stop for all joints
329   */
330  void emergency_stop() {
331    g_emergency_stop = true;
332    safe_stop();
333    RCLCPP_ERROR(this->get_logger(), "Emergency stop triggered");
334  }
335};
336
337/**
338 * @brief Main function
339 */
340int main(int argc, char *argv[]) {
341  rclcpp::init(argc, argv);
342
343  // Set up signal handling
344  signal(SIGINT, signal_handler);
345  signal(SIGTERM, signal_handler);
346
347  try {
348    // Create leg controller node
349    auto leg_node = std::make_shared<JointControllerNode<12, JointArea::LEG>>(
350        "leg_node", "/aima/hal/joint/leg/state", "/aima/hal/joint/leg/command");
351
352    // Create timer node
353    rclcpp::Node::SharedPtr timer_node =
354        rclcpp::Node::make_shared("timer_node");
355    double position = 0.8;
356
357    // Create timer callback function
358    auto timer = timer_node->create_wall_timer(std::chrono::seconds(3), [&]() {
359      if (!g_running || g_emergency_stop)
360        return; // If the program is shutting down or emergency stopped, do not
361                // execute new actions
362      position = -position;
363      position = 1.3 + position;
364      if (!leg_node->SetTargetPosition("left_knee_joint", position)) {
365        RCLCPP_ERROR(rclcpp::get_logger("main"),
366                     "Failed to set target position");
367      }
368    });
369
370    // Create executor
371    rclcpp::executors::MultiThreadedExecutor executor;
372    executor.add_node(leg_node);
373    executor.add_node(timer_node);
374
375    // Main loop
376    while (g_running && rclcpp::ok() && !g_emergency_stop) {
377      executor.spin_once(std::chrono::milliseconds(100));
378    }
379
380    // Safely stop all joints
381    RCLCPP_INFO(rclcpp::get_logger("main"), "Safely stopping all joints...");
382    leg_node->safe_stop();
383
384    // Wait a short time to ensure command transmission is complete
385    std::this_thread::sleep_for(std::chrono::milliseconds(100));
386
387    // Clean up resources
388    RCLCPP_INFO(rclcpp::get_logger("main"), "Cleaning up resources...");
389    leg_node.reset();
390    timer_node.reset();
391
392  } catch (const std::exception &e) {
393    RCLCPP_ERROR(rclcpp::get_logger("main"), "Exception occurred: %s",
394                 e.what());
395    g_emergency_stop = true;
396  } catch (...) {
397    RCLCPP_ERROR(rclcpp::get_logger("main"), "Unknown exception occurred");
398    g_emergency_stop = true;
399  }
400
401  RCLCPP_INFO(rclcpp::get_logger("main"), "Program exited safely");
402  rclcpp::shutdown();
403  return 0;
404}

6.2.10 键盘控制机器人

本示例实现了通过PC的键盘输入控制机器人的前进后退转弯的功能。

通过WASD控制机器人行走方向,增加/减少速度(±0.2 m/s),Q/E增加/减少角速度(±0.1 rad/s),ESC退出程序并释放终端资源,Space立即将速度归零,执行急停

小心

注意:运行本示例前需要先使用手柄,将机器人切入稳定站立模式。(位控站立/走跑模式时按R2+X, 其他模式详见模式流转图进行操作),然后在机器人的终端界面使用:aima em stop-app rc关闭遥控器,防止通道占用。

实现键盘控制前需要注册输入源(该示例已实现注册输入源)
运行前需要安装curse模块:

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

6.2.11 拍照

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

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

6.2.12 相机推流示例集

该示例集提供了多种相机数据订阅和处理功能,支持深度相机、双目相机和单目相机的数据流订阅。
这些相机数据订阅 example 并没有实际的业务用途, 仅提供相机数据基础信息的打印;若您比较熟悉 ros2 使用,会发现 ros2 topic echo + ros2 topic hz 也能够实现 example 提供的功能。 您可以选择快速查阅 SDK 接口手册中话题列表直接快速进入自己模块的开发,也可以使用相机 example 作为脚手架加入自己的业务逻辑。 我们发布的传感器数据均为未经预处理(去畸变等)的原始数据,如果您需要查询传感器的详细信息(如分辨率、焦距等),请关注相应的内参(camera_info)话题。

深度相机数据订阅

该示例中用到了echo_camera_rgbd,通过订阅/aima/hal/sensor/rgbd_head_front/话题来接收机器人的深度相机数据,支持深度点云、深度图、RGB图、压缩RGB图和相机内参等多种数据类型。

功能特点:

  • 支持多种数据类型订阅(深度点云、深度图、RGB图、压缩图、相机内参)

  • 实时FPS统计和数据显示

  • 支持RGB图视频录制功能

  • 可配置的topic类型选择

支持的数据类型:

  • depth_pointcloud: 深度点云数据 (sensor_msgs/PointCloud2)

  • depth_image: 深度图像 (sensor_msgs/Image)

  • rgb_image: RGB图像 (sensor_msgs/Image)

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

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

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

使用说明:

  1. 订阅深度点云数据:

    ros2 run examples echo_camera_rgbd --ros-args -p topic_type:=depth_pointcloud
    
  2. 订阅RGB图像数据:

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

    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
    
  4. 录制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图、压缩图和相机内参数据。

功能特点:

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

  • 实时FPS统计和数据显示

  • 支持RGB图视频录制功能

  • 可配置的topic类型选择

支持的数据类型:

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

使用说明:

  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
    

应用场景:

  • 人脸识别和追踪

  • 目标检测和识别

  • 视觉SLAM

  • 图像处理和计算机视觉算法开发

  • 机器人视觉导航

6.2.13 激光雷达数据订阅

该示例中用到了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.14 播放视频

该示例中用到了play_video,在运行节点程序前,需要先将视频上传到机器人的**交互计算单元(PC3)**上(用户可在其上创建一个用来存储视频的目录),然后将节点程序中的video_path改为需要播放视频的路径。

注意

⚠️ 请注意!交互计算单元(PC3)独立于二开程序所在的开发计算单元(PC2), 音视频文件务必存入交互计算单元(IP: 10.0.1.42)。

功能说明
通过调用PlayVideo服务,可以让机器人在屏幕上播放指定路径的视频文件。请确保视频文件已上传到交互计算单元,否则播放会失败。

 1#include "aimdk_msgs/srv/play_video.hpp"
 2#include "aimdk_msgs/msg/common_request.hpp"
 3#include "rclcpp/rclcpp.hpp"
 4#include <atomic>
 5#include <chrono>
 6#include <memory>
 7#include <string>
 8#include <thread>
 9
10using namespace std::chrono_literals;
11
12class PlayVideoClient : public rclcpp::Node {
13public:
14  PlayVideoClient() : Node("play_video_client"), finished_(false) {
15    client_ = this->create_client<aimdk_msgs::srv::PlayVideo>(
16        "/face_ui_proxy/play_video");
17    RCLCPP_INFO(this->get_logger(), "PlayVideo client node started.");
18  }
19
20  void send_request(const std::string &video_path, uint8_t mode,
21                    uint8_t priority) {
22    while (!client_->wait_for_service(1s) && rclcpp::ok()) {
23      RCLCPP_WARN(this->get_logger(), "Waiting for PlayVideo service...");
24    }
25    if (!rclcpp::ok())
26      return;
27
28    auto request = std::make_shared<aimdk_msgs::srv::PlayVideo::Request>();
29    request->header.header.stamp = this->now();
30    request->video_path = video_path;
31    request->mode = mode;
32    request->priority = priority;
33
34    using ServiceResponseFuture =
35        rclcpp::Client<aimdk_msgs::srv::PlayVideo>::SharedFuture;
36    auto response_callback = [this](ServiceResponseFuture future) {
37      auto response = future.get();
38      if (response->success) {
39        RCLCPP_INFO(this->get_logger(), "✅ Video played successfully: %s",
40                    response->message.c_str());
41      } else {
42        RCLCPP_ERROR(this->get_logger(), "❌ Video play failed: %s",
43                     response->message.c_str());
44      }
45      finished_ = true;
46    };
47    client_->async_send_request(request, response_callback);
48  }
49
50  bool finished() const { return finished_; }
51
52private:
53  rclcpp::Client<aimdk_msgs::srv::PlayVideo>::SharedPtr client_;
54  std::atomic_bool finished_;
55};
56
57int main(int argc, char **argv) {
58  rclcpp::init(argc, argv);
59
60  std::string video_path =
61      "/agibot/data/home/agi/zhiyuan.mp4"; // Default video path; modify as
62                                           // needed
63  int mode = 2;                            // Loop playback
64  std::cout << "Enter video play mode (1: once, 2: loop): " << std::endl;
65  std::cin >> mode;
66  uint8_t priority = 5;
67
68  auto node = std::make_shared<PlayVideoClient>();
69  node->send_request(video_path, mode, priority);
70
71  // Poll using spin_some and check for shutdown
72  while (rclcpp::ok() && !node->finished()) {
73    rclcpp::spin_some(node);
74    std::this_thread::sleep_for(50ms);
75  }
76
77  node.reset();
78  rclcpp::shutdown();
79  return 0;
80}

6.2.15 媒体文件播放

该示例中用到了play_media,通过该节点可实现播放指定的媒体文件(如音频文件),支持WAV、MP3等格式的音频文件播放。

功能特点:

  • 支持多种音频格式播放(WAV、MP3等)

  • 支持优先级控制,可设置播放优先级

  • 支持打断机制,可中断当前播放

  • 支持自定义文件路径和播放参数

  • 提供完整的错误处理和状态反馈

技术实现:

  • 使用PlayMediaFile服务进行媒体文件播放

  • 支持优先级级别设置(0-99)

  • 支持中断控制(is_interrupted参数)

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

应用场景:

  • 音频文件播放和媒体控制

  • 语音提示和音效播放

  • 多媒体应用开发

  • 机器人交互音频反馈

注意

⚠️ 请注意!交互计算单元(PC3)独立于二开程序所在的开发计算单元(PC2), 音视频文件务必存入交互计算单元(IP: 10.0.1.42)。

 1#include <aimdk_msgs/msg/tts_priority_level.hpp>
 2#include <aimdk_msgs/srv/play_media_file.hpp>
 3#include <iostream>
 4#include <rclcpp/rclcpp.hpp>
 5#include <string>
 6
 7using PlayMediaFile = aimdk_msgs::srv::PlayMediaFile;
 8
 9int main(int argc, char **argv) {
10  rclcpp::init(argc, argv);
11  auto node = rclcpp::Node::make_shared("play_media_file_client_min");
12
13  // 1) Service name
14  const std::string service_name = "/aimdk_5Fmsgs/srv/PlayMediaFile";
15  auto client = node->create_client<PlayMediaFile>(service_name);
16
17  // 2) Input file path (prompt user if not provided as argument)
18  std::string default_file =
19      "/agibot/data/var/interaction/tts_cache/normal/demo.wav";
20  std::string file_name;
21
22  if (argc > 1) {
23    file_name = argv[1];
24  } else {
25    std::cout << "Enter the media file path to play (default: " << default_file
26              << "): ";
27    std::getline(std::cin, file_name);
28    if (file_name.empty()) {
29      file_name = default_file;
30    }
31  }
32
33  // 3) Build the request
34  auto req = std::make_shared<PlayMediaFile::Request>();
35  // CommonRequest request -> RequestHeader header -> builtin_interfaces/Time
36  // stamp
37  req->header.header.stamp = node->now();
38
39  // PlayMediaFileRequest required fields
40  req->media_file_req.file_name = file_name;
41  req->media_file_req.domain = "demo_client"; // Required: identifies the caller
42  req->media_file_req.trace_id = "demo";      // Optional: request identifier
43  req->media_file_req.is_interrupted =
44      true; // Whether to interrupt same-priority playback
45  req->media_file_req.priority_weight = 0; // Optional: 0~99
46  // Priority level: default INTERACTION_L6
47  req->media_file_req.priority_level.value = 6;
48
49  // 4) Wait for service and call
50  RCLCPP_INFO(node->get_logger(), "Waiting for service: %s",
51              service_name.c_str());
52  if (!client->wait_for_service(std::chrono::seconds(5))) {
53    RCLCPP_ERROR(node->get_logger(), "Service unavailable: %s",
54                 service_name.c_str());
55    rclcpp::shutdown();
56    return 1;
57  }
58
59  auto future = client->async_send_request(req);
60  auto rc = rclcpp::spin_until_future_complete(node, future,
61                                               std::chrono::seconds(10));
62
63  if (rc == rclcpp::FutureReturnCode::INTERRUPTED) {
64    RCLCPP_WARN(node->get_logger(), "Interrupted while waiting");
65    rclcpp::shutdown();
66    return 1;
67  }
68
69  if (rc != rclcpp::FutureReturnCode::SUCCESS) {
70    RCLCPP_ERROR(node->get_logger(), "Call timed out or did not complete");
71    rclcpp::shutdown();
72    return 1;
73  }
74
75  // 5) Handle response (success is in tts_resp)
76  try {
77    const auto resp = future.get();
78    bool success = resp->tts_resp.is_success;
79
80    if (success) {
81      RCLCPP_INFO(node->get_logger(), "✅ Media file play request succeeded: %s",
82                  file_name.c_str());
83    } else {
84      RCLCPP_ERROR(node->get_logger(), "❌ Media file play request failed: %s",
85                   file_name.c_str());
86    }
87  } catch (const std::exception &e) {
88    RCLCPP_ERROR(node->get_logger(), "Call exception: %s", e.what());
89  }
90
91  rclcpp::shutdown();
92  return 0;
93}

使用说明:

# 播放默认音频文件
ros2 run examples play_media

# 播放指定音频文件
# 注意替换/path/to/your/audio_file.wav为交互板上实际文件路径
ros2 run examples play_media /path/to/your/audio_file.wav

# 播放TTS缓存文件
ros2 run examples play_media /agibot/data/var/interaction/tts_cache/normal/demo.wav

输出示例:

[INFO] [play_media_file_client_min]: ✅ 媒体文件播放请求成功

注意事项:

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

  • 支持的文件格式:WAV、MP3等

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

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

6.2.16 TTS (文字转语音)

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

功能特点:

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

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

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

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

核心代码

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

使用说明

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

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

输出示例

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

注意事项

  • 确保TTS服务正常运行

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

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

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

接口参考

  • 服务:/aimdk_5Fmsgs/srv/PlayTts

  • 消息:aimdk_msgs/srv/PlayTts

6.2.17 麦克风数据接收

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

功能特点:

  • 支持多音频流同时接收(内置麦克风 stream_id=1,外置麦克风 stream_id=2)

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

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

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

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

VAD状态说明:

  • 0: 无语音

  • 1: 语音开始

  • 2: 语音处理中

  • 3: 语音结束

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

使用说明:

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

  2. 音频文件按stream_id分类存储:

    • 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
    

输出示例:

[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.18 表情控制

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

 1#include <aimdk_msgs/msg/common_request.hpp>
 2#include <aimdk_msgs/srv/play_emoji.hpp>
 3#include <chrono>
 4#include <rclcpp/rclcpp.hpp>
 5#include <string>
 6#include <thread>
 7using namespace std::chrono_literals;
 8
 9class PlayEmojiClient : public rclcpp::Node {
10public:
11  PlayEmojiClient() : Node("play_emoji_client"), finished_(false) {
12    client_ = this->create_client<aimdk_msgs::srv::PlayEmoji>(
13        "/face_ui_proxy/play_emoji");
14    RCLCPP_INFO(this->get_logger(), "PlayEmoji client node started.");
15  }
16
17  void send_request(uint8_t emoji, uint8_t mode, uint8_t priority) {
18    while (!client_->wait_for_service(1s) && rclcpp::ok()) {
19      RCLCPP_WARN(this->get_logger(), "Waiting for PlayEmoji service...");
20    }
21    if (!rclcpp::ok())
22      return;
23
24    auto request = std::make_shared<aimdk_msgs::srv::PlayEmoji::Request>();
25    request->header.header.stamp = this->now();
26    request->emotion_id = emoji;
27    request->mode = mode;
28    request->priority = priority;
29
30    using ServiceResponseFuture =
31        rclcpp::Client<aimdk_msgs::srv::PlayEmoji>::SharedFuture;
32    auto response_callback = [this](ServiceResponseFuture future) {
33      auto response = future.get();
34      if (response->success) {
35        RCLCPP_INFO(this->get_logger(), "✅ Emoji played successfully: %s",
36                    response->message.c_str());
37      } else {
38        RCLCPP_ERROR(this->get_logger(), "❌ Emoji play failed: %s",
39                     response->message.c_str());
40      }
41      finished_ = true;
42    };
43    client_->async_send_request(request, response_callback);
44  }
45
46  bool finished() const { return finished_; }
47
48private:
49  rclcpp::Client<aimdk_msgs::srv::PlayEmoji>::SharedPtr client_;
50  std::atomic_bool finished_;
51};
52
53int main(int argc, char **argv) {
54  rclcpp::init(argc, argv);
55
56  PlayEmojiClient client_node;
57
58  int emotion = 1;   // Expression type, 1 means Blink
59  int mode = 1;      // Playback mode, 1 means play once, 2 means loop
60  int priority = 10; // Default priority
61
62  std::cout
63      << "Enter expression ID: 1-Blink, 60-Bored, 70-Abnormal, 80-Sleeping, "
64         "90-Happy, 190-Very Angry, 200-Adoration"
65      << std::endl;
66  std::cin >> emotion;
67  std::cout << "Enter playback mode (1: Play Once, 2: Loop): " << std::endl;
68  std::cin >> mode;
69
70  client_node.send_request(emotion, mode, priority);
71
72  while (rclcpp::ok() && !client_node.finished()) {
73    rclcpp::spin_some(client_node.get_node_base_interface());
74    std::this_thread::sleep_for(100ms);
75  }
76
77  rclcpp::shutdown();
78  return 0;
79}

6.2.19 LED灯带控制

功能说明:演示如何控制机器人的LED灯带,支持多种显示模式和自定义颜色。

核心代码

  1#include <aimdk_msgs/msg/common_request.hpp>
  2#include <aimdk_msgs/srv/led_strip_command.hpp>
  3#include <chrono>
  4#include <memory>
  5#include <rclcpp/rclcpp.hpp>
  6#include <signal.h>
  7#include <string>
  8
  9using namespace std::chrono_literals;
 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    rclcpp::shutdown();
 18  }
 19}
 20
 21class PlayLightsClient : public rclcpp::Node {
 22public:
 23  PlayLightsClient() : Node("play_lights_client") {
 24    client_ = this->create_client<aimdk_msgs::srv::LedStripCommand>(
 25        "/aimdk_5Fmsgs/srv/LedStripCommand");
 26    RCLCPP_INFO(this->get_logger(), "PlayLights client node started.");
 27  }
 28
 29  bool send_request(uint8_t led_mode, uint8_t r, uint8_t g, uint8_t b) {
 30    while (!client_->wait_for_service(1s) && rclcpp::ok()) {
 31      RCLCPP_WARN(this->get_logger(), "Waiting for LedStripCommand service...");
 32    }
 33
 34    if (!rclcpp::ok())
 35      return false;
 36
 37    auto request =
 38        std::make_shared<aimdk_msgs::srv::LedStripCommand::Request>();
 39
 40    request->led_strip_mode = led_mode;
 41    request->r = r;
 42    request->g = g;
 43    request->b = b;
 44
 45    // LED strip is slow to response (up to ~5s)
 46    const std::chrono::milliseconds timeout(5000);
 47
 48    for (int i = 0; i < 4; i++) {
 49      request->request.header.stamp = this->now();
 50      auto future = client_->async_send_request(request);
 51      auto retcode = rclcpp::spin_until_future_complete(
 52          this->shared_from_this(), future, timeout);
 53      if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
 54        // retry as remote peer module can be under heavy load
 55        RCLCPP_INFO(this->get_logger(),
 56                    "trying to send LED strip command... [%d]", i);
 57        continue;
 58      }
 59      // future.done
 60      auto response = future.get();
 61      if (response->status_code == 0) {
 62        RCLCPP_INFO(this->get_logger(),
 63                    "✅ LED strip command sent successfully");
 64        return true;
 65      } else {
 66        RCLCPP_ERROR(this->get_logger(),
 67                     "❌ LED strip command failed with status: %d",
 68                     response->status_code);
 69        return false;
 70      }
 71    }
 72    RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out");
 73    return false;
 74  }
 75
 76private:
 77  rclcpp::Client<aimdk_msgs::srv::LedStripCommand>::SharedPtr client_;
 78};
 79
 80int main(int argc, char **argv) {
 81  try {
 82    rclcpp::init(argc, argv);
 83    signal(SIGINT, signal_handler);
 84    signal(SIGTERM, signal_handler);
 85
 86    auto client_node = std::make_shared<PlayLightsClient>();
 87    g_node = client_node;
 88
 89    int led_mode = 0;          // LED Strip Mode
 90    int r = 255, g = 0, b = 0; // RGB values
 91
 92    std::cout << "=== LED Strip Control Example ===" << std::endl;
 93    std::cout << "Select LED strip mode:" << std::endl;
 94    std::cout << "0 - Steady On" << std::endl;
 95    std::cout << "1 - Breathing (4s period, sinusoidal brightness)"
 96              << std::endl;
 97    std::cout << "2 - Blinking (1s period, 0.5s on, 0.5s off)" << std::endl;
 98    std::cout << "3 - Flow (2s period, lights turn on left to right)"
 99              << std::endl;
100    std::cout << "Enter mode (0-3): ";
101    std::cin >> led_mode;
102
103    std::cout << "\nSet RGB color values (0-255):" << std::endl;
104    std::cout << "Red component (R): ";
105    std::cin >> r;
106    std::cout << "Green component (G): ";
107    std::cin >> g;
108    std::cout << "Blue component (B): ";
109    std::cin >> b;
110
111    // 验证输入范围
112    led_mode = std::max(0, std::min(3, led_mode));
113    r = std::max(0, std::min(255, r));
114    g = std::max(0, std::min(255, g));
115    b = std::max(0, std::min(255, b));
116
117    std::cout << "\nSending LED control command..." << std::endl;
118    std::cout << "Mode: " << led_mode << ", Color: RGB(" << r << ", " << g
119              << ", " << b << ")" << std::endl;
120
121    if (!client_node->send_request(led_mode, r, g, b)) {
122      RCLCPP_ERROR(client_node->get_logger(),
123                   "LED strip control request failed");
124    }
125
126    // Shutdown ROS first, then release resources
127    rclcpp::shutdown();
128    client_node.reset();
129    g_node.reset();
130
131    return 0;
132  } catch (const std::exception &e) {
133    RCLCPP_ERROR(rclcpp::get_logger("main"),
134                 "Program terminated with exception: %s", e.what());
135    rclcpp::shutdown();
136    return 1;
137  }
138}

使用说明

# 构建
colcon build --packages-select examples

# 运行
ros2 run examples play_lights

输出示例

=== LED灯带控制示例 ===
请选择灯带模式:
0 - 常亮模式
1 - 呼吸模式 (4s周期,亮度正弦变化)
2 - 闪烁模式 (1s周期,0.5s亮,0.5s灭)
3 - 流水模式 (2s周期,从左到右依次点亮)
请输入模式 (0-3): 1

请设置RGB颜色值 (0-255):
红色分量 (R): 255
绿色分量 (G): 0
蓝色分量 (B): 0

发送LED控制命令...
模式: 1, 颜色: RGB(255, 0, 0)
✅ LED strip command sent successfully

技术特点

  • 支持4种LED显示模式

  • RGB颜色自定义

  • 异步服务调用

  • 输入参数验证

  • 友好的用户交互界面