键盘控制车子四轮转向

目录

写在前面的话

之前的一篇的文章(底盘四轮转向运动学解析(含代码))设置了四轮转向控制器,通过线速度和角速度计算四个轮子的转速和方向。那下一步需要考虑使用键盘去控制车子运动,参考代码(teleop_twist_keyboard_trio/teleop_keyboard.py)。

遇到的问题

不能通过launch方式启动

会出现如下报错:(意思是系统版本不适配,其实改为run启动方式即可

bash 复制代码
    orig_attr = termios.tcgetattr(self.fd)
termios.error: (25, 'Inappropriate ioctl for device')

安装 gazebo_ros_control

不能缺少 gazebo-ros2-control,否则控制指令无法生效

bash 复制代码
sudo apt install ros-humble-gazebo-ros-pkgs
sudo apt install ros-humble-gazebo-msgs
sudo apt install ros-humble-gazebo-plugins
sudo apt install ros-humble-gazebo-ros2-control 
sudo apt install ros-humble-teleop-twist-keyboard 

官方文档:gazebo_ros2_control

同相模式需要加入y方向线速度

没y方向线速度无法实现同相模式行驶

python 复制代码
# in-phase
elif(mode_selection == 2):

    V = math.hypot(vel_msg.linear.x, vel_msg.linear.y)
    sign = np.sign(vel_msg.linear.x)
    
    if(vel_msg.linear.x != 0):
        ang = vel_msg.linear.y / vel_msg.linear.x
    else:
        ang = 0
    
    self.pos[0] = math.atan(ang)
    self.pos[1] = math.atan(ang)
    self.pos[2] = self.pos[0]
    self.pos[3] = self.pos[1]
    
    self.vel[:] = sign*V    

原地旋转模型需要角速度即可

python 复制代码
# pivot turn
elif(mode_selection == 3):
    
    self.pos[0] = -math.atan(self.wheel_base/self.steering_track)
    self.pos[1] = math.atan(self.wheel_base/self.steering_track)
    self.pos[2] = math.atan(self.wheel_base/self.steering_track)
    self.pos[3] = -math.atan(self.wheel_base/self.steering_track)
    
    self.vel[0] = -vel_msg.angular.z
    self.vel[1] = vel_msg.angular.z
    self.vel[2] = self.vel[0]
    self.vel[3] = self.vel[1]

发布的角速度需要取反

根据实验发现需要角速度取反才可以符合行驶要求

线速度不能过小(反相模式)

由于线速度过小,导致左右前轮转向相反,不符合反相模式。


步骤

完整代码

构建python包

bash 复制代码
ros2 pkg create car_keyboard_controller --build-type ament_python --dependencies rclpy

运行代码

bash 复制代码
ros2 run car_keyboard_controller car_keyboard_controller  

car_keyboard_twist.py

python 复制代码
#!/usr/bin/python3
import math
import threading
import rclpy
import numpy as np
from rclpy.node import Node
from std_msgs.msg import Float64MultiArray
from geometry_msgs.msg import Twist
from sensor_msgs.msg import Joy
import sys, select, termios, tty
import pty




'''
wheel_separation:
    Shortest distance between the left and right wheels. If this parameter is wrong, 
    the robot will not behave correctly in curves.
wheel_base:
    Distance between front and rear wheels, in meters.
wheel_radius:
    Radius of a wheel, i.e., wheels size, used for transformation of linear velocity into wheel rotations. 
    If this parameter is wrong the robot will move faster or slower then expected.
'''


class Commander(Node):

    def __init__(self):
        super().__init__('commander')
        self.wheel_seperation = 1.2
        self.wheel_base = 1
        self.wheel_radius = 0.3
        self.wheel_steering_y_offset = 0.03
        self.steering_track = self.wheel_seperation - 2*self.wheel_steering_y_offset #1.14

        self.pos = np.array([0,0,0,0], float)
        self.vel = np.array([0,0,0,0], float) #left_front, right_front, left_rear, right_rear
                
        # remappings = [('/forward_velocity_controller/commands', '/cmd_vel'),
        #       ('/forward_position_controller/commands', '/cmd_ang')],  ####
        self.pub_pos = self.create_publisher(Float64MultiArray, '/forward_position_controller/commands', 10)
        self.pub_vel = self.create_publisher(Float64MultiArray, '/forward_velocity_controller/commands', 10)

    def pub_pos_vel(self, vel_msg, mode_selection):
        # opposite phase
        if(mode_selection == 1):
            
            vel_steerring_offset = vel_msg.angular.z * self.wheel_steering_y_offset
            sign = np.sign(vel_msg.linear.x)
            

            self.vel[0] = sign*math.hypot(vel_msg.linear.x - vel_msg.angular.z*self.steering_track/2, vel_msg.angular.z*self.wheel_base/2) - vel_steerring_offset
            self.vel[1] = sign*math.hypot(vel_msg.linear.x + vel_msg.angular.z*self.steering_track/2, vel_msg.angular.z*self.wheel_base/2) + vel_steerring_offset
            self.vel[2] = sign*math.hypot(vel_msg.linear.x - vel_msg.angular.z*self.steering_track/2, vel_msg.angular.z*self.wheel_base/2) - vel_steerring_offset
            self.vel[3] = sign*math.hypot(vel_msg.linear.x + vel_msg.angular.z*self.steering_track/2, vel_msg.angular.z*self.wheel_base/2) + vel_steerring_offset

            self.pos[0] = math.atan(vel_msg.angular.z*self.wheel_base/(2*vel_msg.linear.x + vel_msg.angular.z*self.steering_track+0.001))
            self.pos[1] = math.atan(vel_msg.angular.z*self.wheel_base/(2*vel_msg.linear.x - vel_msg.angular.z*self.steering_track+0.001))
            self.pos[2] = -self.pos[0]
            self.pos[3] = -self.pos[1]
            

        # in-phase
        elif(mode_selection == 2):

            V = math.hypot(vel_msg.linear.x, vel_msg.linear.y)
            sign = np.sign(vel_msg.linear.x)
            
            if(vel_msg.linear.x != 0):
                ang = vel_msg.linear.y / vel_msg.linear.x
            else:
                ang = 0
            
            self.pos[0] = math.atan(ang)
            self.pos[1] = math.atan(ang)
            self.pos[2] = self.pos[0]
            self.pos[3] = self.pos[1]
            
            self.vel[:] = sign*V            
            
        # pivot turn
        elif(mode_selection == 3):

            
            self.pos[0] = -math.atan(self.wheel_base/self.steering_track)
            self.pos[1] = math.atan(self.wheel_base/self.steering_track)
            self.pos[2] = math.atan(self.wheel_base/self.steering_track)
            self.pos[3] = -math.atan(self.wheel_base/self.steering_track)
            
            self.vel[0] = -vel_msg.angular.z
            self.vel[1] = vel_msg.angular.z
            self.vel[2] = self.vel[0]
            self.vel[3] = self.vel[1]

        else:
            self.pos[:] = 0
            self.vel[:] = 0

        # fixed by xcg: change the sign of the position
        self.pos = -self.pos
        pos_array = Float64MultiArray(data=self.pos) 
        vel_array = Float64MultiArray(data=self.vel) 
        self.pub_pos.publish(pos_array)
        self.pub_vel.publish(vel_array)
        self.pos[:] = 0
        self.vel[:] = 0

class Keyboard_subscriber(Node):
    def __init__(self):
        super().__init__('keyboard_subscriber')
        self.msg = """
        Reading from the keyboard  and Publishing to Twist!
        ---------------------------
        注意:r是停止,角速度不能设置太大会导致转弯不稳车身摆动,
            要想实现反相效果需要线速度大于角速度,否则车轮转角会不正确。

        Moving around:
        q   w   e (opposite-phase)
        a   s   d   f   g   h   j (in-phase)
        z       c

        anything else : stop

        f/g : run to left_front/right_front (in-phase)
        h/j : run to left_back/right_back (in-phase)
        ,/. : increase/decrease max speed by 10%
        v/b : increase/decrease only linear speed by 10%
        n/m : increase/decrease only angular speed by 10%

        mode_selection
        0 or other_number: 静止(stop 没速度控制)
        1: 低速小角度转弯(oppostise-phase)
        2: 高速变道(in-phase)需要y轴速度
        3: 原地转弯(pviot-turn)


        CTRL-C to quit
        """
        
        self.moveBindings = {
                'w':(1,0,0,0),
                's':(-1,0,0,0),
                'q':(1,0,0,1),
                'e':(1,0,0,-1),
                'a':(0,0,0,1),
                'd':(0,0,0,-1),
                'f':(1,1,0,0),
                'g':(1,-1,0,0),
                'h':(-1,1,0,0),
                'j':(-1,-1,0,0),                   
                'z':(-1,0,0,1),
                'c':(-1,0,0,-1),
                'r':(0,0,0,0),

                # 'W':(1,0,0,0),
                # 'S':(-1,0,0,0),
                # 'Q':(1,0,0,1),
                # 'E':(1,0,0,-1),
                # 'A':(0,0,0,1),
                # 'D':(0,0,0,-1),
                # 'Z':(-1,0,0,1),
                # 'C':(-1,0,0,-1),
                # 'R':(0,0,0,0),
            }
        
        self.speedBindings={
                ',':(1.1,1.1),
                '.':(0.9,0.9),
                'v':(1.1,1),
                'b':(0.9,1),
                'n':(1,1.1),
		        'm':(1,.9),
            }
        
        self.modeBindings={
                '1':'opposite-phase',# 低速转弯
                '2':'in-phase',             
                '3':'pivot-turn',#原地旋转
            }


        # pid = pty.fork()
        # if not pid:
        self.old_settings = termios.tcgetattr(sys.stdin)
        print(self.old_settings)
        self.commander = Commander()
        self.vel_msg = Twist()  # robot velocity
        self.mode_selection = 1 # 1:opposite phase, 2:in-phase, 3:pivot turn 4: none
        self.speed = 0.5
        self.turn = 0.5 #30度左右
        self.x = 0.0
        self.y = 0.0
        self.th = 0.0
        self.status = 0.0


    def getKey(self):
        tty.setraw(sys.stdin.fileno())
        select.select([sys.stdin], [], [], 0)
        key = sys.stdin.read(1)
        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, self.old_settings)
        return key
    
    
    def listener_keyboard(self):

        try:
            print(self.msg)
            print(f"currently: mode_selection({self.mode_selection}) \tspeed({self.vel_msg.linear.x}) \tturn({self.vel_msg.angular.z})")
            while True:
                key = self.getKey()#获取按键
                # print(f'Press {key}')
                if key in self.moveBindings.keys():
                    self.x = float(self.moveBindings[key][0])
                    self.y = float(self.moveBindings[key][1])
                    self.th = float(self.moveBindings[key][3])
                    print(f"currently: mode_selection({self.mode_selection}) \tspeed({self.x * self.speed}) \tturn({self.th * self.turn})")

                
                elif key in self.speedBindings.keys():
                    self.speed = self.speed * self.speedBindings[key][0]
                    self.turn = self.turn * self.speedBindings[key][1]
                    print(f"currently: mode_selection({self.mode_selection}) \tspeed({self.x * self.speed}) \tturn({self.th * self.turn})")

                elif key in self.modeBindings.keys():
                    if self.modeBindings[key] == 'opposite-phase':
                        self.mode_selection = 1
                    elif self.modeBindings[key] == 'in-phase':
                        self.mode_selection = 2
                    elif self.modeBindings[key] == 'pivot-turn':
                        self.mode_selection = 3
                    else:
                        self.mode_selection = 0
                    print(f"currently: mode_selection({self.mode_selection}) \tspeed({self.vel_msg.linear.x}) \tturn({self.vel_msg.angular.z})")
                else:
                    self.x = 0.0
                    self.y = 0.0
                    self.th = 0.0

                    if (self.status == 14):   
                        print(self.msg)
                    self.status = (self.status + 1) % 15

                    if (key == '\x03'):
                        break


                self.vel_msg.linear.x = self.x * self.speed
                self.vel_msg.linear.y = self.y * self.speed
                self.vel_msg.angular.x = 0.0 
                self.vel_msg.angular.y = 0.0 
                self.vel_msg.angular.z = self.th * self.turn
                
                print(f"currently: mode_selection({self.mode_selection}) \tspeed({self.vel_msg.linear.x}) \tturn({self.vel_msg.angular.z})")                
                self.commander.pub_pos_vel(self.vel_msg, self.mode_selection)
        
        except Exception as e:
            print('sss',e)
        
        finally:
            self.vel_msg.linear.x = 0.0; 
            self.vel_msg.linear.y = 0.0; 
            self.vel_msg.linear.z = 0.0
            self.vel_msg.angular.x = 0.0; 
            self.vel_msg.angular.y = 0.0; 
            self.vel_msgangular.z = 0.0
            self.commander.pub_pos_vel(self.vel_msg, self.mode_selection)

            termios.tcsetattr(sys.stdin, termios.TCSADRAIN, self.old_settings)



def main(args=None):
    rclpy.init(args=sys.argv)
    
    keyboard_subscriber = Keyboard_subscriber()
    keyboard_subscriber.listener_keyboard()
    rclpy.spin(keyboard_subscriber) 
    rclpy.shutdown()

if __name__ == '__main__':
    main()

setup.py

加入

car_keyboard_controller = car_keyboard_controller.car_keyboard_twist:main

python 复制代码
from setuptools import find_packages, setup

package_name = 'car_keyboard_controller'

setup(
    name=package_name,
    version='0.0.0',
    packages=find_packages(exclude=['test']),
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='gg',
    maintainer_email='gg@todo.todo',
    description='TODO: Package description',
    license='TODO: License declaration',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'car_keyboard_controller = car_keyboard_controller.car_keyboard_twist:main'
        ],
    },
)

控制演示

视频演示

gazebo

相关推荐
码农小白16 小时前
qt学习:linux监听键盘alt+b和鼠标移动事件
学习·计算机外设
计算机毕设孵化场16 小时前
计算机毕设-基于springboot的多彩吉安红色旅游网站的设计与实现(附源码+lw+ppt+开题报告)
vue.js·spring boot·后端·计算机外设·课程设计·计算机毕设论文·多彩吉安红色旅游网站
kejijianwen1 天前
沸蛇鼠标,多功能智慧AI,重新定义生产力
人工智能·计算机外设
一秒美工助手1 天前
鼠标经过遮罩效果 详情页阿里巴巴国际站外贸跨境电商装修运营 详情页装修无线端装修手机装修设计代码证书滚动特效效果代码,自定义内容代码模板模块设计设置装修
前端·javascript·html·计算机外设
Dnelic-3 天前
查看台式机主机支持的最大分辨率 | 显卡和显示器
计算机外设·计算机显卡
yingge20173 天前
平价鼠标推荐-英菲克PW1有线鼠标
计算机外设
Leoysq4 天前
【UGUI】背包的交互01(道具信息跟随鼠标+道具信息面板显示)
计算机外设
葛狂的博客5 天前
【Qt实现虚拟键盘】
开发语言·qt·计算机外设
Cici_ovo6 天前
MicroPythonBLEHID使用说明——蓝牙鼠标
计算机外设
码码哈哈0.06 天前
微软出品的一个鼠标控制多台电脑,还能共享剪切板分享与下载
microsoft·计算机外设