旭日X3派连接蓝牙与控制小海龟

​ 非常感谢古月居平台推出的高校合作计划,我有幸能够使用旭日X3派进行学习和制作毕业设计。本周使用旭日X3派作为主控芯片,与手柄进行连接,通信,并控制小海龟进行运动。

硬件准备

XBOX one 手柄一个,旭日X3派 (ubuntu20.04 ros2 foxy)

Xbox one 手柄

旭日X3派蓝牙开启

开发板蓝牙功能默认没有开启,需要执行 /usr/bin/startbt6212.sh脚本进行初始化,脚本执行成功后的log如下:

此时,通过ps可看到蓝牙进程已经运行:

root@ubuntu:~# ps ax | grep "/usr/bin/dbus-daemon\|brcm_patchram_plus\|/usr/lib/bluetooth/bluetoothd"
   2695 ?        Ss     0:06 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only
   2963 ?        Ss     0:00 /usr/bin/dbus-daemon --session --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only
   3241 ?        Ss     0:00 /usr/lib/bluetooth/bluetoothd
   5717 pts/0    S+     0:00 grep --color=auto /usr/bin/dbus-daemon\|brcm_patchram_plus\|/usr/lib/bluetooth/bluetoothd

蓝牙信息

XBOX one 手柄驱动安装

首先需要安装手安装joy package包。 joy package为通用的Linux操纵杆提供了ROS驱动,它包括了一个joy_node节点,可以让Linux操纵杆和ROS交互.这个节点发布一个”Joy”消息,包含了操纵杆每一个按钮和轴的当前状态.

sudo apt install ros-foxy-joy

​ 注意我的ubuntu为20.04版本,下载的ros 版本为 ros2 foxy版本。

此时需要下载XBOX 无线驱动

  • 安装dkms

    sudo apt-get install dkms
    
  • 安装xboxdrv

    sudo apt install xboxdrv
    

此时手柄驱动已安装完成。

蓝牙连接手柄

1、命令行连接步骤

  • 执行sudo bluetoothctl进入交互模式下的蓝牙配置界面
sunrise@ubuntu:/dev/input$ sudo bluetoothctl
Agent registered
[bluetooth]#
  • 使用show命令查看蓝牙信息
[bluetooth]# show
Controller 70:F7:54:CD:CB:90 (public)
        Name: ubuntu
        Alias: ubuntu
        Class: 0x00000000
        Powered: no
        Discoverable: no
        DiscoverableTimeout: 0x000000b4
        Pairable: yes
        UUID: A/V Remote Control        (0000110e-0000-1000-8000-00805f9b34fb)
        UUID: PnP Information           (00001200-0000-1000-8000-00805f9b34fb)
        UUID: Message Access Server     (00001132-0000-1000-8000-00805f9b34fb)
        UUID: Message Notification Se.. (00001133-0000-1000-8000-00805f9b34fb)
        UUID: Phonebook Access Server   (0000112f-0000-1000-8000-00805f9b34fb)
        UUID: Generic Access Profile    (00001800-0000-1000-8000-00805f9b34fb)
        UUID: OBEX Object Push          (00001105-0000-1000-8000-00805f9b34fb)
        UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)
        UUID: OBEX File Transfer        (00001106-0000-1000-8000-00805f9b34fb)
        UUID: Vendor specific           (00005005-0000-1000-8000-0002ee000001)
        UUID: A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb)
        UUID: IrMC Sync                 (00001104-0000-1000-8000-00805f9b34fb)
        Modalias: usb:v1D6Bp0246d0535
        Discovering: no
Advertising Features:
        ActiveInstances: 0x00
        SupportedInstances: 0x05
        SupportedIncludes: tx-power
        SupportedIncludes: appearance
        SupportedIncludes: local-name
[bluetooth]#
  • 使用power on 开启蓝牙,

    [bluetooth]# power on
    Changing power on succeeded
    
  • 执行 discoverable on 使得蓝牙可被发现属性

    [bluetooth]# discoverable on
    Changing discoverable on succeeded
    

​ 蓝牙名称为ubuntu

  • 输入scan on打开自动扫描

    再次使用关闭扫描

  • 蓝牙设备配对

    • 配对命令:pair [targetMAC],输入该命令后,根据提示输入yes,并在对端蓝牙设备选择配对选项完成配对
    • 配对成功后,可以使用trust [targetMAC]将设备加入配对列表,下次使用时自动链接
  • 手柄中间X圆键长亮,即为连接成功。

    2、桌面连接

    使用VNC进行远程控制X3派,找到Bluetooth Devices,打开,找到Xbox wireless Controller右键点击连接即可。

蓝牙

测试和校准手柄

jstest-gtk是一个可以测试手柄的工具,可以显示哪个按钮和轴被按下,可以校准和重新为手柄设置每个按钮的索引

  • 安装jstest-gtk

    sudo apt install jsest-gtk
    
  • 在终端输入以下命令,打开jstest-gtk的GUI:

​ 找到Xbox wireless Controller,点击Properties,如果没有,点击刷新,进入的界面如下:

jstesk-gkt

使用手柄控制小海龟

首先创建功能包

代码如下


#include "p9n_node/teleop_twist_joy_node.hpp"

namespace p9n_node
{
TeleopTwistJoyNode::TeleopTwistJoyNode(const rclcpp::NodeOptions & options)
: rclcpp::Node("teleop_twist_joy_node", options)
{
  const std::string hw_name = this->declare_parameter<std::string>(
    "hw_type", p9n_interface::HW_NAME::DUALSENSE);
  this->linear_max_speed_ =
    this->declare_parameter<double>("linear_speed", 0.2);
  this->angular_max_speed_ =
    this->declare_parameter<double>("angular_speed", 0.6);

  try {
    this->hw_type_ = p9n_interface::getHwType(hw_name);
  } catch (std::runtime_error & e) {
    RCLCPP_ERROR(this->get_logger(), e.what());
    RCLCPP_ERROR(
      this->get_logger(), "Please select hardware from %s",
      p9n_interface::getAllHwName().c_str());
    exit(EXIT_FAILURE);
    return;
  }

  this->p9n_if_ =
    std::make_unique<p9n_interface::PlayStationInterface>(this->hw_type_);

  using namespace std::placeholders;  // NOLINT
  this->joy_sub_ = this->create_subscription<Joy>(
    "joy", rclcpp::SensorDataQoS().keep_last(1),
    std::bind(&TeleopTwistJoyNode::onJoy, this, _1));

  this->twist_pub_ = this->create_publisher<Twist>(
    "cmd_vel", rclcpp::QoS(10).reliable().durability_volatile());


  using namespace std::chrono_literals; // NOLINT
  this->timer_watchdog_ = this->create_wall_timer(
    1s, std::bind(&TeleopTwistJoyNode::onWatchdog, this));

  if (this->joy_sub_->get_publisher_count() == 0) {
    RCLCPP_WARN(this->get_logger(), "Joy node not launched");
  }
}

void TeleopTwistJoyNode::onJoy(Joy::ConstSharedPtr joy_msg)
{
  this->timer_watchdog_->reset();
  this->p9n_if_->setJoyMsg(joy_msg);

  static bool stopped = true;
  if (this->p9n_if_->isTiltedStickL()) {
    auto twist_msg = std::make_unique<Twist>();
    twist_msg->linear.set__x(this->linear_max_speed_ * this->p9n_if_->tiltedStickLY());
    twist_msg->angular.set__z(this->angular_max_speed_ * this->p9n_if_->tiltedStickLX());
    this->twist_pub_->publish(std::move(twist_msg));

    stopped = false;
  } else if (!stopped) {
    // Stop cart
    auto twist_msg = std::make_unique<Twist>(rosidl_runtime_cpp::MessageInitialization::ZERO);
    this->twist_pub_->publish(std::move(twist_msg));

    stopped = true;
  }
}

void TeleopTwistJoyNode::onWatchdog()
{
  RCLCPP_WARN(this->get_logger(), "Couldn't subscribe joy topic before timeout");

  // Publish zero velocity to stop vehicle
  auto twist_msg = std::make_unique<Twist>(rosidl_runtime_cpp::MessageInitialization::ZERO);
  this->twist_pub_->publish(std::move(twist_msg));

  this->timer_watchdog_->cancel();
}
}  // namespace p9n_node

配置cmakelists.txt

cmake_minimum_required(VERSION 3.8)
project(p9n_node)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake_auto REQUIRED)
ament_auto_find_build_dependencies()

# Display Node ======================================================
set(TARGET teleop_twist_joy_node)
set(MY_LIB_NAME ${PROJECT_NAME}_${TARGET})
ament_auto_add_library(${MY_LIB_NAME} SHARED src/${TARGET}.cpp)
rclcpp_components_register_node(
  ${MY_LIB_NAME}
  PLUGIN "${PROJECT_NAME}::TeleopTwistJoyNode"
  EXECUTABLE ${TARGET}_exec)

# Testing ===========================================================
if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  set(ament_cmake_copyright_FOUND TRUE)
  set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

ament_auto_package()

创建launch组合功能包

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch.substitutions import TextSubstitution
from launch_ros.actions import ComposableNodeContainer, Node
from launch_ros.descriptions import ComposableNode

def generate_launch_description():
    """Generate launch description."""
    hw_type_arg = DeclareLaunchArgument(
        'hw_type', default_value=TextSubstitution(text='DualSense'))
    topic_name_arg = DeclareLaunchArgument(
        'topic_name', default_value=TextSubstitution(text='/turtle1/cmd_vel'))

    joy_container = ComposableNodeContainer(
        name='joy_container',
        package='rclcpp_components',
        executable='component_container',
        namespace='',
        composable_node_descriptions=[
            ComposableNode(
                package='joy',
                plugin='joy::Joy',
                name='joy',
                namespace='',
            ),
            ComposableNode(
                package='p9n_node',
                plugin='p9n_node::TeleopTwistJoyNode',
                name='teleop_twist_joy_node',
                namespace='',
                parameters=[{
                        'hw_type': LaunchConfiguration('hw_type')
                }],
                remappings=[
                    ('cmd_vel', LaunchConfiguration('topic_name'))
                ],
            )
        ],
    )

    turtlesim = Node(
            name='turtlesim',
            package='turtlesim',
            executable='turtlesim_node'
        )

    ld = LaunchDescription()

    ld.add_action(hw_type_arg)
    ld.add_action(topic_name_arg)

    ld.add_action(joy_container)
    ld.add_action(turtlesim)
    return ld

配置setup.py

"""Setup."""
from glob import glob

from setuptools import setup
package_name = 'p9n_bringup'
setup(
    name=package_name,
    version='1.1.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        ('share/{}/launch'.format(package_name), glob('launch/*.launch.py')),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='m12watanabe1a',
    maintainer_email='40206149+m12watanabe1a@users.noreply.github.com',
    description='PlayStation Interface Node Launch Files.',
    license='Apache License 2.0',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
        ],
    },
)

运行如下命令:

  • 创建工作空间

    mkdir joy_test
    cd joy_test
    
  • 复制程序

    git clone  https://gitee.com/xiaogujiayou/ros2_joy.git
    
  • 编译

    colcon build
    
  • 添加环境变量

  source install/setup.sh
  • 运行程序

      ros2 launch p9n_bringup teleop_turtlesim.launch.py
    

即可看到现象,如下:

ROS2 手柄控制小海龟