ROS_Python编程 之 案例代码核心解析(第一版)

2023-05-16

ROS_Python编程 之 案例代码核心解析(第一版)

通过Handsfree mini机器人平台配套的中级教程,我对ros_python编程实现 传感器数据读取、运动控制 的知识做以下归纳:

文章目录

    • ROS_Python编程 之 案例代码核心解析(第一版)
      • 1. 传感器数据读取
        • 1.1 获取机器人底层硬件基本信息并打印
        • 1.2 获取IMU数据,并转化成欧拉角打印
        • 1.3 获取激光雷达数据并打印
        • 1.4 获取摄像头数据,并用opencv_python库显示
      • 2. 控制机器人移动
        • 2.1 控制机器人直线运动
        • 2.2 控制机器人转向运动
        • 2.3 编写一个键盘遥控程序

1. 传感器数据读取

1.1 获取机器人底层硬件基本信息并打印

# 核心代码
#!/usr/bin/env
#coding=UTF-8 

import rospy
from handsfree_msgs.msg import robot_state

def callback(data): #回调函数
    rospy.loginfo("the embedded system_time: %fus",data.system_time) #下位机系统时间 
    rospy.loginfo("the embedded cpu temperature is: %f",data.cpu_temperature) #cpu温度
    rospy.loginfo("the battery voltage is: %f",data.battery_voltage) #电池电压
    rospy.loginfo("the battery power remain is: percent %f",data.power_remain*100) #电池电量剩余
    rospy.loginfo("——————————————————————————————————————— \n\r") 

def sub_state():
    rospy.init_node('sub_state', anonymous=True)
    rospy.Subscriber("/handsfree/robot_state", robot_state, callback) 
    rospy.spin()

if __name__ == '__main__':
    sub_state()

精髓:rospy.Subscriber("/handsfree/robot_state", robot_state, callback)

其中,话题订阅者Subscriber对象的构造函数的第二个参数是所要订阅的话题类型

1.2 获取IMU数据,并转化成欧拉角打印

 #!/usr/bin/env
 #coding=UTF-8

 import rospy
 import tf
 from tf.transformations import *
 from sensor_msgs.msg import Imu

 def callback(data):
     #这个函数是tf中的,可以将四元数转成欧拉角
     (r,p,y) = tf.transformations.euler_from_quaternion((data.orientation.x,data.orientation.y,data.orientation.z,data.orientation.w))
     #由于是弧度制,下面将其改成角度制看起来更方便
     rospy.loginfo("Pitch = %f,Roll = %f, Yaw = %f",r*180/3.1415926,p*180/3.1415926,y*180/3.1415926)

 def get_imu():
     rospy.init_node('get_imu', anonymous=True)
     rospy.Subscriber("/handsfree/imu", Imu, callback)
     rospy.spin()

 if __name__ == '__main__':
     get_imu()

精髓:(r,p,y)= tf.transformations.euler_from_quaternion((data.orientation.x,data.orientation.y,data.orientation.z,data.orientation.w))

其中,ros中欧拉角与四元数的互相转化可以如下实现:

# 欧拉角转四元数
pos = Pose()
q = tf.transformations.quaternion_from_euler(0, 0, point.z)
pos.orientation.x = q[0]
pos.orientation.y = q[1]
pos.orientation.z = q[2]
pos.orientation.w = q[3]

# 四元数转欧拉角
(r, p, y) = tf.transformations.euler_from_quaternion([msg.orientation.x, msg.orientation.y, msg.orientation.z, msg.orientation.w])
sell.fill_euler_msg(msg, r, p, y)

1.3 获取激光雷达数据并打印

激光雷达数据是指雷达通过发射激光束探测目标的位置后,将接收到的从目标反射回来的信号(目标回波)与发射信号进行比较,作适当处理后,就可获得目标的有关信息。一般,激光雷达的驱动程序会将其封装好通过topic发布出来。

 #!/usr/bin/env
 #coding=UTF-8 

 import rospy
 from sensor_msgs.msg import LaserScan

 def callback(data):
     rospy.loginfo("the angle_min is %f",data.angle_min) #打印一些信息
     rospy.loginfo("the angle_max is %f",data.angle_max)
     rospy.loginfo("the scan data is ")
     for i in range(0, len(data.ranges)):
         print data.ranges[i]
     print "\n"

 def sub_state():
     rospy.init_node('sub_scan', anonymous=True)
     rospy.Subscriber("/scan", LaserScan, callback)
     rospy.spin()

 if __name__ == '__main__':
     sub_state()

1.4 获取摄像头数据,并用opencv_python库显示

摄像头数据就是图像,是指摄像头获取的彩色rgb图像和深度depth图像等,一般都将其封装在摄像头驱动之中,直接调用即可

 #!/usr/bin/env
 #coding=UTF-8

 import cv2
 import rospy
 from sensor_msgs.msg import Image
 from cv_bridge import CvBridge, CvBridgeError

 def callback(data):
     try:
         cv_image = CvBridge().imgmsg_to_cv2(data, "bgr8") #使用cv_bridge将其转化为mat类型
     except CvBridgeError as e:
         print(e)
     (rows,cols,channels) = cv_image.shape
     cv2.imshow("Image window", cv_image) #显示出图像
     cv2.waitKey(30) #30ms 后播放下一帧

 def get_photo():
     rospy.init_node('get_photo', anonymous=True)
     rospy.Subscriber("/camera/rgb/image_raw", Image, callback)
     rospy.spin() 
	   
 if __name__ == '__main__':
     get_photo()

精髓:cv_image = CvBridge().imgmsg_to_cv2(data, "bgr8")

其中,CvBridge用于连接ros信息和opencv信息

2. 控制机器人移动

2.1 控制机器人直线运动

handsfree mini 机器人工控机自带的ros系统一共有两份代码,分别是 linear_move.py 和 linear_move_by_srv.py

  • linear_move.py 是通过改变代码的参数配置来传参
  • linear_move_by_srv.py 是通过调用Service接口来传参

其中,linear_move.py 源码如下:

#!/usr/bin/env python
import tf
import math
import rospy
import geometry_msgs.msg

class LinearMove(object):
    def __init__(self):
        self.frame_base = rospy.get_param('~base_frame', '/base_link')
        self.frame_odom = rospy.get_param('~odom_frame', '/odom')
        self.velocity_topic = rospy.get_param('~vel_topic', '/mobile_base/mobile_base_controller/cmd_vel')
        self.tolerance_distance = rospy.get_param('~tolerance', 0.08) # m
        self.speed_linear = rospy.get_param('~speed_linear', 0.1) # m/s
        self.rate_pub = rospy.get_param('~velocity_pub_rate', 10) # 10Hz(一秒10次)
        self.rate = rospy.Rate(self.rate_pub)
        self.vel_pub = rospy.Publisher(self.velocity_topic,
                                       geometry_msgs.msg.Twist,
                                       queue_size=1)
        self.tf_listener = tf.TransformListener()
        rospy.on_shutdown(self.brake)
        try:
            self.tf_listener.waitForTransform(self.frame_odom,
                                              self.frame_base,
                                              rospy.Time(0),
                                              rospy.Duration(5.0))
        except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException):
            rospy.logerr('tf catch exception when robot waiting for transform......')
            exit(-1)

    def move_to_target(self, dis_to_move=0):
        """
        to make robot move forward/back dis_to_move meters
        :param: dis_to_move: the distance make robot move, >0 means move forward, <0 means move back
        :type: float
        :return:
        """
        self.robot_start_pos = self.__get_robot_pos()
        rospy.logdebug("****************************************************************************")
        rospy.logdebug("robot current position is x = %f, y = %f, try to move forward/back %f Meter"
                       %(self.robot_start_pos.x, self.robot_start_pos.y, dis_to_move))
        rospy.logdebug("****************************************************************************")
        while self.__is_robot_arrived(dis_to_move) is not True:
            self.__move_robot(direction=(1 if dis_to_move > 0 else -1))
            self.rate.sleep()
        self.brake()  # we have arrived the target position, so stop robot
        rospy.loginfo('arrived the target point')

    def __is_robot_arrived(self, dis_to_move):
        """
        to check has the robot arrived target position
        :param: dis_to_move: the distance thar robot needs to move forward/back
        :type: float
        :return: False --- robot has not arrived the target position
                 True --- robot has arrived the target position
        """
        robot_cur_pos = self.__get_robot_pos()
        dis_moved = math.sqrt(math.pow((robot_cur_pos.x - self.robot_start_pos.x), 2) +
                                    math.pow((robot_cur_pos.y - self.robot_start_pos.y), 2))
        dis_need_move = math.fabs(dis_to_move) - dis_moved
        return False if math.fabs(dis_need_move) > self.tolerance_distance else True

    def __move_robot(self, direction=1):
        """
        send velocity to robot according to the direction
        :param: direction: when direction = 1: make robot move forward
                when direction = -1: make robot move back
        :type: int
        :return:
        """
        move_cmd = geometry_msgs.msg.Twist()
        move_cmd.linear.x = math.copysign(self.speed_linear, direction)
        self.vel_pub.publish(move_cmd)

    def __get_robot_pos(self):
        """
        to get current position(x,y,z) of robot
        :return: A geometry_msgs.msg.Point type store robot's position (x,y,z)
        """
        try:
            (trans, rot) = self.tf_listener.lookupTransform(self.frame_odom,
                                                            self.frame_base,
                                                            rospy.Time(0))
        except (tf.Exception, tf.ConnectivityException, tf.LookupException):
            rospy.logerr('tf catch exception when robot looking up transform')
            exit(-1)
        return geometry_msgs.msg.Point(*trans)

    def brake(self):
        """
        send command to stop the robot
        :return:
        """
        self.vel_pub.publish(geometry_msgs.msg.Twist())


if __name__ == '__main__':
    rospy.init_node('LinearMove')
    t_dis = rospy.get_param('~t_dis', 0.2) # m
    if t_dis == 0.0:
        rospy.logerr('no target distance set!')
        exit(-1)
    rospy.loginfo('try to move %f meters'%t_dis)
    LinearMove().move_to_target(dis_to_move=t_dis)
# 精髓1 geometry_msgs.msg.Twist
self.vel_pub = rospy.Publisher(self.velocity_topic,geometry_msgs.msg.Twist,
                               queue_size=1)

geometry_msgs.msg.Twist 是一种消息类,原始定义如下:

Vector3  linear
Vector3  angular

紧凑定义如下:

geometry_msgs/Vector3 linear
geometry_msgs/Vector3 angular
# 精髓2  tf.TransformListener类
self.tf_listener = tf.TransformListener()

self.tf_listener.waitForTransform(self.frame_odom,
                                  self.frame_base,
                                  rospy.Time(0),
                                  rospy.Duration(5.0))

通过监听tf,可以避免繁琐的旋转矩阵的计算,而直接获取相关信息。在监听中,最常用的两个函数是:tf.TransformListener.lookupTransform() 和 tf.TransformListener.transformPoint()。

  1. tf.TransformListener.lookupTransform():

(1)功能:获得从第一个坐标系到第二个坐标系转变的位姿关系,包括旋转和平移;rospy.Time(0)指最近时刻存储的数据,不可以改成rospy.Time.now(),rospy.Time.now()指现在时刻,因为监听数据需要进行缓存,无法实时检测,会有几毫秒的延迟

(2)参数与返回值:第一个参数是第一个坐标系,第二个参数是第二个坐标系,第三个参数是需要进行转变的时间;返回值 (trans, rot) 分别是 translation平移 和 rotation旋转,其中rot是四元数(quaternion)形式

(3)实例函数:

# 获取机器人当前位姿的函数,其中self.tf_listener.lookupTransform(self.frame_odom,self.frame_base,rospy.Time(0))函数获取了odom与base_link转变的位姿,也即机器人较原点移动的位姿,从而获取当前状态
def __get_robot_pos(self):
    try:
        (trans, rot) = self.tf_listener.lookupTransform(self.frame_odom,
                                                        self.frame_base,
                                                        rospy.Time(0))
    except (tf.Exception, tf.ConnectivityException, tf.LookupException):
        rospy.logerr('tf catch exception when robot looking up transform')
        exit(-1)
        return geometry_msgs.msg.Point(*trans)
  1. tf.TransformListener.transformPoint():

(1)功能:可以将一个坐标系下的点的坐标转换到另一个坐标系下

(2)参数:

# m_normal_pose的数据类型是geometry_msgs::PointStamped,需要定义m_normal_pose.header.frame_id即该点所属的坐标系
# "PTAM_world"则是坐标转换的结果,数据类型同样为geometry_msgs::PointStamped
listener_.transformPoint("PTAM_world",m_normal_pose,pose_PTAM_world)

另外,linear_move_by_srv.py 源码的精髓如下:

service_move_dis = rospy.Service('move_distance', handsfree_srv.SpecialMove, callback_linear_move)

2.2 控制机器人转向运动

通过python程序控制机器人沿z轴左右转动即偏航,代码与直线运动代码差不多,handsfree mini 机器人工控机自带的ros系统一共有三份代码,分别是 radian_turn.py,radian_turn_by_srv.py 和 radian_turn_direct_by_srv.py

  • radian_turn.py 是通过改变代码的参数配置来传参
  • radian_turn_by_srv.py 和 radian_turn_direct_by_srv.py 是通过调用Service接口来传参
  • radian_turn.py 和 radian_turn_by_srv.py 只会在-180~180度之间旋转(如:如果运行这两个代码,并传递参数6.28,机器人是不会动的,而运行 radian_turn_direct_by_srv.py,机器人会转一圈)
  • 逆时针旋转,角度为正;顺时针旋转,角度为负

其中,radian_turn.py 的主要函数如下:

def __get_robot_pos(self):
    try:
        trans, rot = self.tf_listener.lookupTransform(self.frame_odom,
                                                      self.frame_base,
                                                      rospy.Time(0))
    except (tf.Exception, tf.ConnectivityException, tf.LookupException):
        rospy.logerr('tf catch exception when robot looking up transform')
        exit(-1)
        return tf.transformations.euler_from_quaternion(rot)[2]

def __turn_robot(self, turn_direction=1):
    """
    send velocity command to robot according to the direction
    :param: direction: when direction = 1: make robot turn in clockwise
        when direction = -1: make robot turn in counterclockwise
    :type: int
    :return:
    """
    move_cmd = geometry_msgs.msg.Twist()
    move_cmd.angular.z = math.copysign(self.speed_radian, turn_direction)
    self.vel_pub.publish(move_cmd)    
    
def turn_to_target(self, radian_to_turn=0.0):
    # yaw [-pi,pi]->[0,2*pi)
    robot_start_yaw = (self.__get_robot_pos() + math.pi*2) % (math.pi*2)

    # return target_yaw = radian_to_turn + robot_start_yaw 
    target_yaw = math.copysign(math.fabs(radian_to_turn)%(math.pi*2), radian_to_turn)+robot_start_yaw

    # transform the range of target_yaw
    target_yaw = (target_yaw + math.pi*2) % (math.pi*2)

    # to find the shortest direction to turn
    radian_to_move = target_yaw - robot_start_yaw
    if radian_to_move < -math.pi or math.pi < radian_to_move:
        direction = 1 if radian_to_move < -math.pi else -1
    else:
        direction = 1 if radian_to_move > 0 else -1
    self.brake()  # to stop robot first
    rospy.logdebug("****************************************************************************")
    rospy.logdebug("the robot's Yaw = %f, try to turn to Yaw = %f, the direction = %d"
                  %(robot_start_yaw, target_yaw, direction))
    rospy.logdebug("****************************************************************************")
    while self.__is_robot_arrived(target_yaw) is not True:
        self.__turn_robot(turn_direction=direction)
        self.rate.sleep()
    self.brake()
    rospy.loginfo('arrived the target radian!')

知识学习:欧拉角的理解

旋转永远是做游戏的难点和混乱点,表示旋转有很多种方式,比如简单的欧拉角、较复杂的四元数、更复杂的矩阵。

欧拉角是表达旋转的最简单方式,形式上是一个三维向量,其值分别代表物体绕坐标系三轴(x,y,z)的旋转角度。显然,不同的旋转顺序定义会产生不同的旋转结果,所以一般引擎都会规定自己的旋转顺序。

绕三轴的旋转值pitch,yaw,roll说法来自航空界,分别翻译为:pitch(俯仰角),yaw(偏航角),roll(翻滚角),

24

注意:pitch,yaw,roll与x,y,z没有明确固定的对应顺序,只是人为定义而已,因此可能存在差异!!!

另外,pitch角度值介于[-90,90]【万向锁】,yaw角度值介于[-180,180]【越过180°、-180°前后,最近转动方向相反(参考下文精髓4配图)】


而 handsfree mini 机器人工控机自带ros系统中,target_yaw 对应 move_cmd.angular.z

# 精髓1 tf.transformations.euler_from_quaternion()
return tf.transformations.euler_from_quaternion(rot)[2] #根据欧拉角列表下标,此处代码返回的是yaw轴角度值
# 精髓2 
def turn_to_target(self, radian_to_turn=0.0):
   robot_start_yaw = (self.__get_robot_pos() + math.pi*2) % (math.pi*2)

# 解析:
# yaw∈[-pi,pi], (yaw+2*pi)∈[pi,3*pi], (yaw+2*pi)%(2*pi)∈[0,2*pi)
# 精髓3
def turn_to_target(self, radian_to_turn=0.0):
   target_yaw = math.copysign(math.fabs(radian_to_turn)%(math.pi*2), radian_to_turn)+robot_start_yaw

# 解析1:math.fabs(radian_to_turn)%(math.pi*2)
# radian_to_turn表示还需要转动的角度值(弧度制),math.fabs()取绝对值确保计算得到的角度值为正

# 解析2:math.copysign()
# math.copysign(math.fabs(radian_to_turn)%(math.pi*2), radian_to_turn)返回一个浮点数,该浮点数由第一个参数math.fabs(radian_to_turn)%(math.pi*2)【即还需要转动的弧度制角度值(正角)】的值和第二个参数radian_to_turn【即还需要转动的弧度制角度值(正负不定)】的正负号组成
# 精髓4
radian_to_move = target_yaw-robot_start_yaw
if radian_to_move < -math.pi or math.pi < radian_to_move:
    direction = 1 if radian_to_move < -math.pi else -1
else:
    direction = 1 if radian_to_move > 0 else -1

# 解析见下图:

25

radian_turn_direct_by_srv.py 的精髓如下:

def callback_turn_radian(req):
    radian_to_turn=req.target_radian_turn  
    while radian_to_turn > 3.14:
        radian_to_turn = radian_to_turn - 3.14;
        turn_radian.turn_to_target(3.14)
    while radian_to_turn < -3.14:
        radian_to_turn = radian_to_turn + 3.14;
        turn_radian.turn_to_target(-3.14)
    turn_radian.turn_to_target(radian_to_turn)
    return handsfree_srv.SpecialTurnResponse(True)

# 解析:
# 由于yaw角度值介于[-180,180]【越过180°、-180°前后,最近转动方向相反】,故源码radian_turn.py和radian_turn_by_srv.py只能在-180~180度之间旋转,不能一次性旋转超过半圈;而源码radian_turn_direct_by_srv.py可以旋转超过半圈,新增函数的功能是:如果需要旋转超过半圈,采用分两次旋转,即先旋转半圈,再旋转剩余的角度(备注:这只是一种理解方式,实际运行时机器人旋转是连贯的)

2.3 编写一个键盘遥控程序

到目前为止,我陆陆续续亲自接触过几台机器人小车,比如racecar、spark、mini等,在诸多源码中,键盘遥控程序是从不缺席的。反观这些源码,可以发现键盘遥控程序几乎都大同小异。因此,我在此注解一下 handsfree mini 机器人的键盘遥控程序,以后需要编写键盘遥控程序时均可参考。

#!/usr/bin/env python
import roslib
import rospy
from geometry_msgs.msg import Twist
import sys, select, termios, tty

msg = """
Reading from the keyboard and Publishing to Twist!
---------------------------
Moving around:
   u    i    o
   j    k    l
   m    ,    .
For Holonomic mode (strafing), hold down the shift key:
---------------------------
   U    I    O
   J    K    L
   M    <    >
t : up (+z)
b : down (-z)
anything else : stop
q/z : increase/decrease max speeds by 10%
w/x : increase/decrease only linear speed by 10%
e/c : increase/decrease only angular speed by 10%
CTRL-C to quit
"""

moveBindings = {
		'i':(1,0,0,0),
		'o':(1,0,0,-1),
		'j':(0,0,0,1),
		'l':(0,0,0,-1),
		'u':(1,0,0,1),
		',':(-1,0,0,0),
		'.':(-1,0,0,1),
		'm':(-1,0,0,-1),
		'O':(1,-1,0,0),
		'I':(1,0,0,0),
		'J':(0,1,0,0),
		'L':(0,-1,0,0),
		'U':(1,1,0,0),
		'<':(-1,0,0,0),
		'>':(-1,-1,0,0),
		'M':(-1,1,0,0),
		't':(0,0,1,0),
		'b':(0,0,-1,0),
	       }

speedBindings={
		'q':(1.1,1.1),
		'z':(.9,.9),
		'w':(1.1,1),
		'x':(.9,1),
		'e':(1,1.1),
		'c':(1,.9),
	      }

def getKey():
	# tty.setraw(fd, when=termios.TCSAFLUSH)将文件描述符fd的模式更改为raw。
	# 如果when被省略,则默认为termios.TCSAFLUSH,并传递给termios.tcsetattr()
	tty.setraw(sys.stdin.fileno())
    # int select(int maxfdpl, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval * timeout),第一个参数是最大的文件描述符长度,第二个是需要监听可读的套接字, 第三个是需要监听可写的套接字, 第四个是需要监听异常的套接字, 第五个是时间限制设置。其他源码运用了该函数的返回值:如果监听的套接字满足了可读可写条件, 那么所返回的can_read 或 can_write就会有值, 然后就可以利用这些返回值进行后续操作
	select.select([sys.stdin], [], [], 0)
	key = sys.stdin.read(1)
	# int tcsetattr(int fd, int optional_actions, const struct termios *termios_p)
	# tcsetattr函数用于设置终端参数。函数运行成功时返回0,失败时返回-1
	# 参数fd为打开的终端文件描述符,参数optional_actions用于控制修改起作用的时间,而结构体termios_p中保存了要修改的参数
	# optional_actions可以取如下的值:TCSANOW(不等数据传输完毕就立即改变属性),TCSADRAIN(等待所有数据传输结束才改变属性),
	# TCSAFLUSH:等待所有数据传输结束, 清空输入输出缓冲区才改变属性
	termios.tcsetattr(sys.stdin, termios.TCSADRAIN, settings)
	return key

speed = 0.30
turn = 0.6

def vels(speed,turn):
	return "currently:\tspeed %s\tturn %s " % (speed,turn)

if __name__ == "__main__":
	# sys.stdin表示标准化输入,termios.tcgetattr(fd)返回一个包含文件描述符fd的tty属性的列表
	settings = termios.tcgetattr(sys.stdin)
	
	pub = rospy.Publisher('/mobile_base/mobile_base_controller/cmd_vel', Twist, queue_size = 1)
	rospy.init_node('teleop_twist_keyboard')

	x = 0
	y = 0
	z = 0
	th = 0
	status = 0

	try:
		print msg
		print vels(speed,turn)
		while(1):
			key = getKey()
			if key in moveBindings.keys():
				x = moveBindings[key][0]
				y = moveBindings[key][1]
				z = moveBindings[key][2]
				th = moveBindings[key][3]
			elif key in speedBindings.keys():
				speed = speed * speedBindings[key][0]
				turn = turn * speedBindings[key][1]
				print vels(speed,turn)
				if (status == 14):
					print msg
				status = (status + 1) % 15
				x = 0
				y = 0
				z = 0
				th = 0
			else:
				x = 0
				y = 0
				z = 0
				th = 0
				if (key == '\x03'): # ctrl+c
					break

			twist = Twist()
			twist.linear.x = x*speed; twist.linear.y = y*speed; twist.linear.z = z*speed;
			twist.angular.x = 0; twist.angular.y = 0; twist.angular.z = th*turn #只有偏航角
			pub.publish(twist)

	except:
		print e

	finally:
		twist = Twist()
		twist.linear.x = 0; twist.linear.y = 0; twist.linear.z = 0
		twist.angular.x = 0; twist.angular.y = 0; twist.angular.z = 0
		pub.publish(twist)

    	termios.tcsetattr(sys.stdin, termios.TCSADRAIN, settings)

# 精髓1 用二维字典定义按键对应的运动信息(包括运动方向和速度大小)
moveBindings = {
		'i':(1,0,0,0),
		'o':(1,0,0,-1),
		'j':(0,0,0,1),
		'l':(0,0,0,-1),
		'u':(1,0,0,1),
		',':(-1,0,0,0),
		'.':(-1,0,0,1),
		'm':(-1,0,0,-1),
		'O':(1,-1,0,0),
		'I':(1,0,0,0),
		'J':(0,1,0,0),
		'L':(0,-1,0,0),
		'U':(1,1,0,0),
		'<':(-1,0,0,0),
		'>':(-1,-1,0,0),
		'M':(-1,1,0,0),
		't':(0,0,1,0),
		'b':(0,0,-1,0),
	       }

speedBindings={
		'q':(1.1,1.1),
		'z':(.9,.9),
		'w':(1.1,1),
		'x':(.9,1),
		'e':(1,1.1),
		'c':(1,.9),
	      }

# 解析:
# 经测试,用于定义按键对应运动"方向"信息的二维字典是任意赋值的,也就是说 +1、0、-1 是随机组合的(数字位数也没有限制),即便将各个按键对应的值全部改成(1,0,0,0)或(1,0,0),机器人也可以正常运动,不会发生按键冲突
# 精髓2 python获取键盘按键信息
def getKey():
	# tty.setraw(fd, when=termios.TCSAFLUSH)将文件描述符fd的模式更改为raw。
	# 如果when被省略,则默认为termios.TCSAFLUSH,并传递给termios.tcsetattr()
	tty.setraw(sys.stdin.fileno())
	# 第一个参数是需要监听可读的套接字, 第二个是需要监听可写的套接字, 第三个是需要监听异常的套接字, 第四个是时间限制设置
	# 如果监听的套接字满足了可读可写条件, 那么所返回的can_read 或 can_write就会有值, 然后就可以利用这些返回值进行后续操作
	select.select([sys.stdin], [], [], 0)
	key = sys.stdin.read(1)
	# int tcsetattr(int fd, int optional_actions, const struct termios *termios_p)
	# tcsetattr函数用于设置终端参数。函数运行成功时返回0,失败时返回-1
	# 参数fd为打开的终端文件描述符,参数optional_actions用于控制修改起作用的时间,而结构体termios_p中保存了要修改的参数
	# optional_actions可以取如下的值:TCSANOW(不等数据传输完毕就立即改变属性),TCSADRAIN(等待所有数据传输结束才改变属性),
	# TCSAFLUSH:等待所有数据传输结束, 清空输入输出缓冲区才改变属性
	termios.tcsetattr(sys.stdin, termios.TCSADRAIN, settings)
	return key
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ROS_Python编程 之 案例代码核心解析(第一版) 的相关文章

  • 机载计算机控制APM固件飞控的缺点

    APM固件 xff1a 机载计算机通过mavlink控制apm飞控的能力有限 经过尝试 xff0c mavros控制apm飞控情况如下 目前只支持 xff1a 控制三个姿态角和推力 xff08 需要自己写姿态控制环 xff09 xff0c
  • APM固件自动巡航车测评与SLAM路径规划

    笔者以为多旋翼无人机相比无人车来讲更落地些 在特定场合下的比无人机更落地 无人驾驶技术经过谷歌 xff0c 百度等巨头公司的普及 xff0c 逐渐在一些路段不是很复杂的公路开始运行 在园区 xff0c 公园 xff0c 工厂等特定场合的AG
  • 无人机PX4固件—传感器多冗余机制测评讨论

    在开源无人机领域 xff0c 传感器多冗余一直是PIXHAWK这款飞控的有别于其他开源飞控的特色和硬件成比较高的地方 我们从PIXHAWK控制板的硬件和软件两方面结合LOG日志来分析它的传感器多冗余机制 MPU6000 3轴加速度和3轴陀螺
  • 国内唯一系统教程资料Nuttx操作系统移植教程—PIXAWK飞控平台移植必备

    我们筹备大半年之久的 Nuttx操作系 统移植视频教程套件 终于在年前可以发布了 我们知道Ardupoilt固件和PX4固件的飞控系统都是基于Nuttx这个操作系统的 xff0c 这个操作系统是一个类似LINUX的操作系统 xff0c 具有
  • PX4四旋翼mavros与offboard模式在避障实时路径规划(SLAM)方面的应用培训班

    前言 基于SLAM的机器人技术 xff0c 一直是机器人领域的研究热点 xff0c 伴随了一个又一个无人车项目的落地 xff0c SLAM技术到了一个又一个的研究高潮 我们知道PX4构架下面的 mavros与offboard模式 xff0c
  • PIXHAWK无人机mavros与SLAM技术实用课程培训--北京站(2018年3月31日~4月2日)

    SLAM技术一直是机器人领域的研究热点 伴随着一个又一个无人车SLAM的落地 xff0c 技术的发展到了实用化的研究热潮 xff0c 尤其SLAM技术在无人机路径规划方面的研究更具无限潜力 xff0c 广阔的天地间毕竟还需要那么一双眼睛 众
  • ardupilot官方2018年项目计划表(建议)

    本文是一篇ArduPilot开发人员为GSoC2018建议的项目列表 xff0c 我们翻译过来方便大家观看 xff0c 文章来源 xff1a http ardupilot org dev docs gsoc ideas list html
  • offboard模式下的无人机自主飞行(基于树莓派,ROS,MAVROS)

    offboard模式下 xff08 基于树莓派 xff0c ROS xff0c MAVROS 无人机自主飞行测试视频链接地址 xff1a https v qq com x page t0633a3ykta html 我们在无人机视觉方面做了
  • 2018年4月最新版虚拟机(包含APM开发环境和PX4开发环境)

    以下为 2018年4月最新版代码编译 的虚拟机 xff08 包含APM开发环境和PX4开发环境 xff09 分享给一直支持阿木的大家 百度网盘下载地址 xff1a 链接 xff1a https pan baidu com s 1n9Pydi
  • 阿木社区pixhawk二次开发无人机参数测量报告

    为了更好的给无人机建模 xff0c 更好的给无人机建立数学模型 xff0c 用于算法开发 xff0c 我们测量了数据如下 xff1a 1 无人机绕三轴转动惯量的测量 在此我们利用双线摆来测量三个转动惯量 xff0c 其示意图和原理如下 xf
  • 千寻高精度定位系统能在pixhawk系统上使用成功吗?

    如何在不使用基站的情况下 xff0c 得到精确的位置数据 xff1f 如何给在无人车的开发提供全国范围的厘米级精确定位数据 xff1f 如何在基于pixhawk上的无人车 xff0c 无人船 xff0c 无人机上解决以上问题 xff1f 为
  • 在ubuntu server上安装raspi-config并开启CSI摄像头

    我的树莓派安装了ubuntu server18 04 xff0c 由于不是树莓派官方系统raspian xff0c 因此不自带raspi config 启动摄像头需要用到官方的raspi config配置程序 xff0c 进入官网地址 xf
  • 【测试】QGC地面站开发课程完结篇--一站多机控制测试说明

    阿木实验室去年推出的QGC地面站开发实战课程 xff0c 随着地面站控制多架飞机的测试的成功 xff0c 课程全部完结 xff0c 以下是我们户外测试最终版地面站的测试视频 xff1a 视频地址 xff1a https v qq com x
  • sdf文件使用plugin

  • VGG16训练RAF-DB

    使用VGG16对本地数据集RAF DB中的basic图片进行训练 xff0c 官方已经在图片命名时分好了train与test xff0c train和test的label在同一个txt文件里 xff0c 方便起见 xff0c 把这两种lab
  • CMake编译opencv(测试)

    WORKIGN FOR THE WOLF 单编译OpenCV来测试项目 项目名称 span class token operator span 自定义 span class token function project span span
  • 从零开始学习SLAM:openCV

    继续跟随 视觉SLAM十四讲 学习SLAM问题 xff0c 由于理论方面已经有一些研究 xff0c 主要缺乏的是在LINUX下的实战开发能力 xff0c 因而从代码开始分析入手 xff0c 同时对C 43 43 11进行回顾 1 openC
  • 对博士学位说永别

    来自王垠 xff1a http blog sina com cn s blog 5d90e82f0101atzr html 经过深思熟虑之后 xff0c 我决定再次 抛弃 我的博士学位 这是我第三次决定离开博士学位 xff0c 也应该是最后
  • python web开发——Django基于类的视图

    简介 视图是一个可调用对象 xff0c 可以接收一个请求然后返回一个响应 这个可调用对象不仅仅限于函数 xff0c Django 同时提供一些可以用作视图的类 它们允许你结构化你的视图并且利用继承和混合重用代码 后面我们将介绍一些用于简单任
  • 使用docker安装ubuntu镜像

    使用docker安装ubuntu镜像 查找Ubuntu镜像 docker search ubuntu 安装Ubuntu镜像 docker pull ubuntu 查看docker镜像 docker images 运行docker镜像 doc

随机推荐

  • Ubuntu安装kalibr

    Ubuntu安装kalibr错误集锦 一 安装过程 ros参考 xff1a https blog csdn net Mua111 article details 107513509 kalibr安装参考 xff1a https blog c
  • 树莓派4b ubuntu系统开启串口

    树莓派4b安装ubuntu server18后如何开启串口 xff1f 树莓派4b的引脚图如下 xff1a 其中GPIO14和GPIO15是硬件串口 因为我安装的不是Raspian系统 xff0c 因此无法用raspi config打开该串
  • 惯性导航原理(1):导航坐标系及相互转换

    一 导航坐标系转换 坐标系介绍1 惯性坐标系 xff08 地心惯性坐标系 xff09 i系2 地球坐标系 xff08 地心地固坐标系 xff09 e系3 WGS 84坐标系 xff08 常用 xff09 blh坐标系4 当地水平地理坐标系g
  • win7系统下安装Ubuntu20.04.5系统保姆级教程

    一 制作u盘启动盘 准备工作 xff1a 一个空的8G大小的u盘 43 ultraISO软件 43 ubuntu系统的镜像文件 1 下载并安装ultraISO软件 下载地址 xff1a 百度网盘 请输入提取码 提取码 xff1a jv6a
  • 手把手带你免费打嘉立创pcb板

    手把手带你免费打嘉立创pcb板 前言一 熟悉规则二 下单1 下载安装下单助手2 领劵 注意 前言 嘉立创的新规则 xff1a 上个月消费没有满20的话只支持立创EDA画的板子 一 熟悉规则 嘉立创的免费规则和板子工艺要求如下 xff0c 大
  • ADRC学习与参数整定心得

    ADRC xff0c 中文名是自抗扰控制技术 继承了经典PID控制器的精华 xff0c 对被控对象的数学模型几乎没有任何要求 xff0c 又在其基础上引入了基于现代控制理论的状态观测器技术 xff0c 将抗干扰技术融入到了传统PID控制当中
  • ArUco相关

    ArUco相关 ArUco xff0c 一个开源的微型的现实增强库 https blog csdn net bashendixie5 article details 113769010 Aruco码估计相机位姿初步 xff01 xff01
  • ArUco

    文章目录 一 ArUco简介二 Marker和字典三 步骤1 创建Marker xff08 Marker Creation xff09 2 检测Marker xff08 Marker Detection xff09 3 姿态估计 xff08
  • Python PIP升级后执行命令报错: sys.stderr.write(f“ERROR: {exc}“)解决方法

    近日在使用pip时终端始终提示 You are using pip version 8 1 1 however version 21 0 1 is available You should consider upgrading via th
  • 戴尔Optiplex-7080装ubuntu16.04双系统时遇到的一些坑

    目录 ubuntu16 04安装过程中遇到的坑安装教程安装类型为空 xff08 读取不到磁盘 xff09 ubuntu16 04安装完成后无法启动windows ubuntu16 04无法连接无线网络 ubuntu16 04安装过程中遇到的
  • Ubuntu 16.04无法检测双屏/nvidia-smi显示no running processing found/nvidia-settings读取不到显卡信息

    问题 xff1a 1 笔记本ubuntu 16 04系统无法检测双屏 xff0c xrandr不显示hdmi接口或hdmi disconnectted 2 nvidia smi的最下方显示No running processes found
  • C++中的智能指针:shared_ptr

    本文主要参考 std shared ptr C 43 43 shared ptr共享型智能指针详解 std shared ptr 是一种智能指针 xff0c 它能够记录多少个 shared ptr 共同指向一个对象 xff0c 从而消除显式
  • pixhawk4 mini重启后死机

    pixhawk4 mini通过供电口供电 xff0c telement1口连接数传 xff0c usb口连接树莓派 xff08 机载计算机 xff09 此时如果用QGC地面站数传发送reboot指令 xff0c pixhawk4 mini关
  • C++利用auto对std::vector进行遍历时auto的类型

    先把结论摆在最前面 xff0c 当使用for auto amp node nodes 对std vector lt xxx gt nodes进行遍历时 xff0c auto定义的node其实是一个对nodes中对应元素的引用 具体的探究过程
  • 使用roslaunch为Gazebo加载自定义模型时黑屏、报错问题

    今天自己弄了一个dae类型的模型文件 xff0c 想要导入gazebo xff0c 按照gazebo world文件中导入dae模型的问题文章操作了一番 xff0c 编写如下 world文件 span class token operato
  • 点云地图导入gazebo思路

    参考这篇帖子下曹超大神的回答 xff0c 思路如下 xff1a 曹超CMU xff1a 如果不需要用地图渲染rgb图像的话 xff0c 我们通常的做法是把纯激光雷达点云地图导入CloudCompare进行downsample和计算每个点的n
  • Git删除历史commit记录中的大文件

    在使用Git的过程中 xff0c 常常会出现不小心commit了没用作用的大文件 xff0c 导致无法push到远程的情况 xff0c 并且即使删除了该文件重新commit xff0c 该文件也已经保存在历史commit中 xff0c 仍然
  • realsense d435 刷固件后连接失败问题

    刷固件后提示 Backend in rs2 create device info list 000001B00E9DF470 index 0 source gt QueryInterface uuidof IKsTopologyInfo r
  • 用Stm32CubeMX在STM32F107上移植LWIP(PHY:DM9161A)

    背景 有一块吃灰7年的神州IV号开发板 xff0c 主控芯片STM32F107VCT6 xff0c PHY芯片DM9161A xff0c 配套的资料都是当年ST的标准库 这个开发板应该是因为当年上市太匆忙 xff0c 资料和代码的细节部分做
  • ROS_Python编程 之 案例代码核心解析(第一版)

    ROS Python编程 之 案例代码核心解析 xff08 第一版 xff09 通过Handsfree mini机器人平台配套的中级教程 xff0c 我对ros python编程实现 传感器数据读取 运动控制 的知识做以下归纳 xff1a