5. 二次开发程序部署

5. 二次开发程序部署

二次开发程序一般推荐两种部署方式,分别为本体 HDU 、 ADU 部署和三方工控机部署,本体部署硬件上较为简洁,适用于二开程序资源占用不大的情况,如需占用较多资源则需将 HDU 、 ADU 上原有的部分程序关闭释放 CPU/GPU 等资源;三方工控机部署则需使用额外的传输方式传输数据,如http传输协议,好处是原有机上软件程序功能都可以保留,同时又有相对充足的算力资源。

5.1 本体 HDU 、 ADU 部署

推荐的程序部署目录为 /agibot/data/home/agi/Desktop,机上磁盘清理模块会定时清理空间,该文件夹为白名单文件夹不会被清理。

bash
mkdir -p /agibot/data/home/agi/Desktop

如遇权限问题,可考虑使用如下命令进行文件夹创建

bash
sudo mkdir -p /agibot/data/home/agi/Desktop
sudo chown -R agi:agi /agibot/data/home/agi

5.1.1 Python 虚拟环境

使用 Python 语言进行二次开发只需创建虚拟环境即可,可使用 conda/venv 等工具创建虚拟环境,其中 venv 为系统自带工具,conda 安装可以参考 miniconda 安装官方文档。

5.1.2 C++ 开发部署

使用 C++ 编译型语言,若使用x86_64架构开发程序,需要在x86_64架构的本地电脑上进行编译,编译完成后将可执行文件上传到 HDU 、 ADU 上即可。若使用arm64架构开发程序,需要在arm64架构的本地电脑上进行编译型语言进行二次开发。 若在x86_64架构的本地电脑上进行编译,需要安装arm64架构的编译环境,具体步骤如下

1、安装交叉编译工具链

bash
sudo apt install \
gcc-aarch64-linux-gnu \
g++-aarch64-linux-gnu \
binutils-aarch64-linux-gnu

2、在x86_64架构的本地电脑上构建目标设备的 sysrootA3u ,否则可能出现头文件或库版本不匹配,具体步骤如下

(1)可在工程目录下创建sysrootA3u目录

(2)从目标机器上拷贝/lib,/usr/lib, /usr/include ,/usr/local,/usr/share ,/opt/ros目录到 sysrootA3u 目录下

bash
scp -r agi@172.23.20.140:/lib sysrootA3u/
scp -r agi@172.23.20.140:/usr/lib sysrootA3u/
scp -r agi@172.23.20.140:/usr/include sysrootA3u/
scp -r agi@172.23.20.140:/usr/local sysrootA3u/
scp -r agi@172.23.20.140:/usr/share sysrootA3u/
scp -r agi@172.23.20.140:/opt/ros sysrootA3u/

(3)下载目标设备上的第三方依赖库

3、下面以wake_up程序为例,展示如何在x86_64架构的本地电脑上进行交叉编译。

(1)构建工程目录

bash
temp/
├── build-a3u/
│   └── wakeup_subscriber
├── prebuilt/
│
├── sysrootA3u/
│   ├── lib/
│   │
│   ├── opt/
│   │   └── ros/
│   └── usr/
│       ├── include/
│       ├── lib/
│       ├── local/
│       └── share/
│
├── CMakeLists.txt
├── main.cpp
└── start_wakeup_subscriber.sh

(2)依赖安装

程序依赖protobuf库,需安装protobuf编译库,拷贝的sysrootA3u中仅含有运行库,下载链接

https://deb.debian.org/debian/pool/main/p/protobuf/libprotobuf-dev_3.21.12-3+deb12u1_amd64.deb

解压到sysrootA3u指定目录,不安装到系统中

bash
  dpkg-deb -x  libprotobuf-dev_3.21.12-3+deb12u1_amd64.deb  /agibot/data/home/agi/Desktop/temp/sysrootA3u

(3)wake_up demo程序

bash

#include <cstdint>
#include <limits>
#include <memory>
#include <string>

#include <google/protobuf/util/json_util.h>
#include <rclcpp/rclcpp.hpp>
#include <ros2_plugin_proto/msg/ros_msg_wrapper.hpp>

#include "aimdk/protocol/agent/agent_data.pb.h"

namespace
{
constexpr char kTopic[] =
   "/agent/wakeup/pb_3Aaimdk_2Eprotocol_2EWakeUpResult";
}

class WakeUpSubscriber final : public rclcpp::Node
{
public:
   WakeUpSubscriber()
      : Node("wakeup_result_subscriber")
   {
      const auto qos = rclcpp::QoS(rclcpp::KeepLast(10))
                           .reliable()
                           .durability_volatile();

      subscription_ =
            create_subscription<ros2_plugin_proto::msg::RosMsgWrapper>(
               kTopic,
               qos,
               [this](
                  const ros2_plugin_proto::msg::RosMsgWrapper::ConstSharedPtr msg) {
                  wakeup_callback(msg);
               });

      RCLCPP_INFO(get_logger(), "已开始订阅 WakeUpResult: %s", kTopic);
   }

private:
   void wakeup_callback(
      const ros2_plugin_proto::msg::RosMsgWrapper::ConstSharedPtr &msg)
   {
      if (msg->serialization_type != "pb") {
            RCLCPP_WARN(
               get_logger(),
               "收到不支持的序列化类型: %s",
               msg->serialization_type.c_str());
            return;
      }

      if (msg->data.size() >
            static_cast<std::size_t>(std::numeric_limits<int>::max())) {
            RCLCPP_ERROR(get_logger(), "WakeUpResult 数据长度超出解析限制");
            return;
      }

      aimdk::protocol::WakeUpResult wakeup_result;
      if (!wakeup_result.ParseFromArray(
               msg->data.data(),
               static_cast<int>(msg->data.size()))) {
            RCLCPP_ERROR(get_logger(), "解析 WakeUpResult 数据失败");
            return;
      }

      google::protobuf::util::JsonPrintOptions options;
      options.add_whitespace = true;
      options.preserve_proto_field_names = true;

      std::string json;
      const auto status = google::protobuf::util::MessageToJsonString(
            wakeup_result,
            &json,
            options);

      if (!status.ok()) {
            RCLCPP_ERROR(
               get_logger(),
               "WakeUpResult 转换为 JSON 失败: %s",
               status.ToString().c_str());
            return;
      }

      RCLCPP_INFO(get_logger(), "WakeUpResult: %s", json.c_str());
   }

   rclcpp::Subscription<
      ros2_plugin_proto::msg::RosMsgWrapper>::SharedPtr subscription_;
};

int main(int argc, char *argv[])
{
   rclcpp::init(argc, argv);
   const auto node = std::make_shared<WakeUpSubscriber>();

   RCLCPP_INFO(
      node->get_logger(),
      "正在监听 WakeUpResult,按 Ctrl+C 退出...");

   rclcpp::spin(node);

   RCLCPP_INFO(node->get_logger(), "退出中...");
   rclcpp::shutdown();
   return 0;
}

(4)CMakeLists.txt文件

bash
cmake_minimum_required(VERSION 3.22)

# 交叉编译器必须是能在 x86 主机运行的程序,不能使用 sysroot 内的 ARM64 编译器包装程序。
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_C_COMPILER /usr/bin/aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER /usr/bin/aarch64-linux-gnu-g++)

project(wakeup_subscriber LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

set(PROJECT_ROOT "${CMAKE_CURRENT_SOURCE_DIR}")
set(SYSROOT "${PROJECT_ROOT}/sysrootA3u")
set(AARCH64_PREBUILT "${PROJECT_ROOT}/prebuilt/ros2_plugin_proto_aarch64")
set(AARCH64_TRIPLE "aarch64-linux-gnu")

set(CMAKE_SYSROOT "${SYSROOT}")
set(CMAKE_FIND_ROOT_PATH
   "${AARCH64_PREBUILT}"
   "${SYSROOT}"
)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

list(PREPEND CMAKE_PREFIX_PATH
   "${SYSROOT}/opt/ros/jazzy"
   "${AARCH64_PREBUILT}"
)

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(ros2_plugin_proto REQUIRED)

set(Protobuf_INCLUDE_DIR "${SYSROOT}/usr/include")
set(Protobuf_LIBRARY "${SYSROOT}/usr/lib/${AARCH64_TRIPLE}/libprotobuf.so.32")
find_package(Protobuf 3.21.12 REQUIRED)

add_executable(wakeup_subscriber
   main.cpp
   "${PROJECT_ROOT}/prebuilt/generated/aimdk/protocol/agent/agent_data.pb.cc"
   "${PROJECT_ROOT}/prebuilt/generated/aimdk/protocol/common/control_source.pb.cc"
   "${PROJECT_ROOT}/prebuilt/generated/aimdk/protocol/common/header.pb.cc"
   "${PROJECT_ROOT}/prebuilt/generated/aimdk/protocol/common/rpc.pb.cc"
   "${PROJECT_ROOT}/prebuilt/generated/aimdk/protocol/common/timestamp.pb.cc"
)

target_include_directories(wakeup_subscriber SYSTEM PRIVATE
   "${SYSROOT}/usr/include/${AARCH64_TRIPLE}"
)

target_include_directories(wakeup_subscriber PRIVATE
   "${PROJECT_ROOT}/prebuilt/generated"
   "${AARCH64_PREBUILT}/include"
)

target_link_libraries(wakeup_subscriber PRIVATE
   protobuf::libprotobuf
   rclcpp::rclcpp
   ros2_plugin_proto::ros2_plugin_proto__rosidl_typesupport_cpp
)

set_target_properties(wakeup_subscriber PROPERTIES
   BUILD_RPATH "${AARCH64_PREBUILT}/lib;${SYSROOT}/opt/ros/jazzy/lib;${SYSROOT}/usr/lib/${AARCH64_TRIPLE};${SYSROOT}/lib/${AARCH64_TRIPLE}"
   INSTALL_RPATH "$ORIGIN/../lib"
)

install(TARGETS wakeup_subscriber
   DESTINATION bin
)

(5)编译程序

bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j"$(nproc)"

(6)运行程序 程序的运行依赖prebuilt/audio_msgs_proto_aarch64/lib下的库文件,可通过以下命令运行

bash
source /agibot/software/v0/entry/env/env.sh
source /agibot/data/home/agi/Desktop/prebuilt/ros2_plugin_proto_aarch64/share/ros2_plugin_proto/local_setup.bash
./build-a3u/wakeup_subscriber

5.2 有线连接机器人设备

普通外设连接

机器人头部具有与HDU设备连接的Type-C接口,用于与HDU设备进行通信。用户可将机器人头部的Type-C接口连接到其他设备上,即可实现与HDU设备的通信。

第三方工控机连接

如若需要在有线连接机器人设备时进行 ROS2 通信,需要进行如下操作:

首先将第三方工控机连接到机器人背部调试仓网口处,修改工控机网段为10.42.10.x,然后将 HDU 上的 /agibot/software/v0/entry/cfg/ros_dds_configuration.xml 拷贝到本地电脑,在本地电脑上执行 ROS2 程序或使用 ros2 命令前执行如下命令(其中 /path/to 需要替换为真实路径)

bash
export ROS_DOMAIN_ID=232
export FASTRTPS_DEFAULT_PROFILES_FILE=/path/to/ros_dds_configuration.xml