跳到主要内容

运动控制

控制行走

调用 python 模块

为了方便开发调用,我们将控制机器人行走的方法封装成模块。leju_lib_pkg 是我们提供的 python 接口库,该库存在于 ~/robot_ros_application/catkin_ws/src/leju_lib_pkg

walk 函数

def walk(self):
if self.__check_id() != True:
return False
status = self.__get_status()
if status == None:
return -1
if status.data == 'walking':
return True
if self.ready() == True:
result = self.__set_status(self.ctl_id, 'walking')
if result == None:
return -2
if result == self.__state_walking:
return True
return False
  • 走路之前需要调用该方法设置机器人的状态为走路状态,否则机器人将不会执行走路

walking 函数

# path: ~/robot_ros_application/catkin_ws/src/leju_lib_pkg/src/motion/bodyhub_client.py
def walking(self, delta_x, delta_y, theta, timeout = None):
try:
rospy.wait_for_message('/requestGaitCommand', Bool, self.__timeout if timeout is None else timeout)
except:
rospy.logwarn("wait_for_service requestGaitCommand timeout!")
result = 1
else:
result = 0
self.__is_walking = 1
self.__gait_cmd_pub.publish(data=[delta_x, delta_y, theta])
return result
  • 这是最底层的行走封装,有基于该函数封装的更高层函数

  • walking 函数直接向 /requestGaitCommand 发送单步信息,包括 delta_x(前后),delta_y(左右)以及 theta(旋转)

  • 以右手定则划分,向前、向左和逆时针为正方向,反之为负方向

  • 如果 delta_x、delta_y 或 theta 参数不合理(超过了机器人所能到达的极限),会出现逆解失败( ik failed!!! )的警告,同时机器人不会执行这步调用。因此不建议开发者直接对该函数进行调用,可以用以下几个函数,都对机器人的最大步幅做了限制

walking_the_distance 函数

# path: ~/robot_ros_application/catkin_ws/src/leju_lib_pkg/src/motion/bodyhub_client.py
def walking_the_distance(self, delta_x, delta_y, theta):
direction = [0, 0, 0]
direction[0] = 1 if delta_x >= 0 else -1
direction[1] = 1 if delta_y >= 0 else -1
direction[2] = 1 if theta >= 0 else -1

xNumberOfStep = abs(delta_x)//self.__step_len_max[0]
yNumberOfStep = abs(delta_y)//self.__step_len_max[1]
aNumberOfStep = abs(theta)//self.__step_len_max[2]
delta_x = delta_x - ((self.__step_len_max[0] * xNumberOfStep)*direction[0])
delta_y = delta_y - ((self.__step_len_max[1] * yNumberOfStep)*direction[1])
theta = theta - ((self.__step_len_max[2] * aNumberOfStep)*direction[2])
numberOfStep = max(xNumberOfStep, yNumberOfStep, aNumberOfStep)

for i in range(0, int(numberOfStep+1)):
if xNumberOfStep >= 1:
x = self.__step_len_max[0]*direction[0]
xNumberOfStep = xNumberOfStep - 1
elif xNumberOfStep == 0:
x = delta_x
xNumberOfStep = xNumberOfStep - 1
else:
x = 0.0

if yNumberOfStep >= 1:
y = self.__step_len_max[1]*direction[1]
yNumberOfStep = yNumberOfStep - 1
elif yNumberOfStep == 0:
y = delta_y
yNumberOfStep = yNumberOfStep - 1
else:
y = 0.0

if aNumberOfStep >= 1:
a = self.__step_len_max[2]*direction[2]
aNumberOfStep = aNumberOfStep - 1
elif aNumberOfStep == 0:
a = theta
aNumberOfStep = aNumberOfStep - 1
else:
a = 0.0
self.walking(x, y, a)
  • 该函数允许用户传入要走的距离及旋转的角度,函数中会将距离按照机器人行走的最大限幅划分步数。参数传递与 walking 相同,只不过在这个函数,你可以直接传目标点与机器人的距离了

  • 在属性 self.__step_len_max 中限定了机器人的限幅,避免了出现逆解失败的情况

walking_n_steps 函数

# path: ~/robot_ros_application/catkin_ws/src/leju_lib_pkg/src/motion/bodyhub_client.py
def walking_n_steps(self, cmd, n):
for i in range(0, n):
if self.walking(cmd[0], cmd[1], cmd[2]):
return 1
return 0
  • 该函数允许用户传入单个步幅及步数,但同时需要在传入时限制步幅大小

  • 参数 cmd 是一个列表,定义如下

    • @cmd: [delta_x, delta_y, theta]

    • 与 walking 函数的参数相同,相当于是可以传入步数的 walking 函数

  • 参数 n 即为步数,该值应该大于 0

Python 示例

#!/usr/bin python
# -*- coding: utf-8 -*-
import rospy
import rospkg
import sys

sys.path.append(rospkg.RosPack().get_path('leju_lib_pkg'))
import motion.bodyhub_client as bodycli

CTL_ID = 2

class Walking(bodycli.BodyhubClient):
def __init__(self):
super(Walking, self).__init__(CTL_ID)

def do_some_walking(self):
# 走 S 形曲线

# 方法一 调用 walking 函数
for i in range(0, 5):
self.walking(0.0, 0.0, 12.0)
for i in range(0, 16):
self.walking(0.07, 0.0, -7.5)
for i in range(0, 16):
self.walking(0.07, 0.0, 7.5)
for i in range(0, 5):
self.walking(0.0, 0.0, -12.0)

# 方法二 调用 walking_the_distance 函数
# self.walking_the_distance(0.0, 0.0, 60.0)
# self.walking_the_distance(1.12, 0.0, -120.0)
# self.walking_the_distance(1.12, 0.0, 120.0)
# self.walking_the_distance(0.0, 0.0, -60.0)

# 方法三 调用 walk_n_step 函数
# self.walk_n_step([0.0, 0.0, 12.0], 5)
# self.walk_n_step([0.07, 0.0, -7.5], 16)
# self.walk_n_step([0.07, 0.0, 7.5], 16)
# self.walk_n_step([0.0, 0.0, -12.0], 5)

self.wait_walking_done()

def start(self):
self.walk()
self.do_some_walking()
rospy.signal_shutdown('exit')

def rosShutdownHook(self):
self.reset()

if __name__ == "__main__":
rospy.init_node('walking_node', anonymous=True)
walking = Walking()
rospy.on_shutdown(walking.rosShutdownHook)
walking.start()

控制末端轨迹

调用 Python 模块

  • 末端轨迹的控制是通过 ikmodule 逆解模块实现的,模块定义在 ~/robot_ros_application/catkin_ws/src/leju_lib_pkg/src/ik_lib/ikmodulesim/ikmodulesim.py 下面将介绍调用的接口函数:

toInitPoses 函数

  • 函数定义如下:

    def toInitPoses(self):
    if self.setstatus() and self.getposes():
    self.PosPara_wF.Torso_z = -self.leftLegZ
    self.PosPara_wF.Lfoot_y = self.leftLegY
    self.PosPara_wF.Rfoot_y = self.rightLegY
    self.PosPara_wF.RArm_x = self.rightArmX
    self.PosPara_wF.RArm_y = self.rightArmY
    self.PosPara_wF.RArm_z = self.rightArmZ
    self.PosPara_wF.LArm_x = self.leftArmX
    self.PosPara_wF.LArm_y = self.leftArmY
    self.PosPara_wF.LArm_z = self.leftArmZ
    return True
    return False
  • 初始化 ikmodule,必须在调用 body_motion 前调用

  • 初始化成功返回 True,否则返回 False

末端位置

  • 在 ~/robot_ros_application/catkin_ws/src/leju_lib_pkg/src/ik_lib/ikmodulesim/CtrlType.py 的 CtrlType 类中定义了末端位置。末端位置在 body_motion 函数 中用到,用于指定需要控制的末端位置

  • 定义了如下末端点

    定义含义
    Torso躯干质心
    Torso_R躯干质心绕 x 轴方向的旋转,逆时针为正
    Torso_P躯干质心绕 y 轴方向的旋转,逆时针为正
    Torso_Y躯干质心绕 z 轴方向的旋转,逆时针为正
    Torso_x躯干质心沿 x 轴方向的位移,向前为正
    Torso_y躯干质心沿 y 轴方向的位移,向左为正
    Torso_z躯干质心沿 z 轴方向的位移,向上为正
    Lfoot左脚
    Lfoot_R左脚绕 x 轴方向的旋转,逆时针为正
    Lfoot_P左脚绕 y 轴方向的旋转,逆时针为正
    Lfoot_Y左脚绕 z 轴方向的旋转,逆时针为正
    Lfoot_x左脚沿 x 轴方向的位移,向前为正
    Lfoot_y左脚沿 y 轴方向的位移,向左为正
    Lfoot_z左脚沿 z 轴方向的位移,向上为正
    Rfoot右脚
    Rfoot_...与左脚类似
    LArm_x左手沿 x 轴方向的位移,向前为正
    LArm_y左手沿 y 轴方向的位移,向左为正
    LArm_z左手沿 z 轴方向的位移,向上为正
    RArm_...与左手类似

body_motion 函数

  • 函数定义如下:

    def body_motion(self, body_pose, value, count=100):
    """
    简易调用
    Args:
    - body_pose: CtrlType, or [CtrlType]
    - value: list, or two dim list. RPY, xyz.
    - count: int. times of division

    """
    if not (isinstance(body_pose, list) or isinstance(body_pose, tuple)):
    body_pose = [body_pose]
    value = [value]

    d = {}

    for t, diff in zip(body_pose, value):
    if isinstance(t.key(), str):
    d[t.key()] = diff
    else:
    for key, dif in zip(t.key(), diff):
    d[key] = dif

    for index in range(count):
    for k, v in d.items():
    setattr(self.PosPara_wF, k, getattr(self.PosPara_wF, k) + v * 1.0 / count)

    poseArrayMsg = PoseArray()
    poseArrayMsg.poses.append(self.getleftlegPosMsg())
    poseArrayMsg.poses.append(self.getrightlegPosMsg())
    poseArrayMsg.poses.append(self.getleftarmPosMsg())
    poseArrayMsg.poses.append(self.getrightarmPosMsg())
    poseArrayMsg.controlId = 6
    self.targetPosesPub.publish(poseArrayMsg)

    rospy.loginfo("waitPostureDone...")
    self.waitPostureDone()
    • 机器⼈末端动作执⾏,给定⽬标位姿 body_pose,value 通过逆解得到舵机角度,控制机器⼈运动

    • 该函数需要传递三个参数

      • body_pose

        • 需要控制的末端位置,可以只传一个,如果要传多个需要放在列表内传入,如:

          body_motion(末端位置, ...)body_motion([末端位置1, 末端位置2], ...)

      • value

        • 表⽰给相应末端的位姿值,一个末端位置应该对应一个位姿值,如:

          body_motion(末端位置, 位姿值, ...)body_motion([末端位置1, 末端位置2], [位姿值1, 位姿值2], ...)

        • 位姿值的定义

          • 如果是 Torso、Lfoot 或 Rfoot ,那么位姿值是一个列表,包含,定义如下:

            [x, y, z, R, P, Y]

          • 如果是指定了轴的旋转或或位移,如:Torso_y,只需要传躯干沿 y 轴方向的位移即可

        • 位姿值的单位

          • 位移的单位为米(m),旋转的单位为度(°)
      • count

        • 插值次数

reset 函数

  • 函数定义如下:

    def reset(self):
    rospy.wait_for_service("MediumSize/IKmodule/SetStatus", 30)
    try:
    resp = self.setStatusClient.call(6, "reset")
    rospy.loginfo("Set status with response: %d" % resp.stateRes)
    return True
    except rospy.ServiceException as e:
    rospy.logwarn("Reset status service call failed: %s" % e)
  • 用于动作执行完成之后的复位

Python 示例

#!/usr/local/bin/python
# -*- coding: utf-8 -*-
import rospy
import math

from ik_lib.ikmodulesim import IkModuleSim
from ik_lib.ikmodulesim.CtrlType import CtrlType as C
from mediumsize_msgs.srv import *

class IkAction(IkModuleSim):
def __init__(self):
super(IkAction, self).__init__() # 继承 IkModuleSim

def do_some_action(self):
if self.toInitPoses() == True: # 初始化 Ik
self.body_motion(C.Torso, [0, 0, -0.04, 0, 0, 0]) # 躯干质心沿 z 轴向下 4cm ,即机器人会下蹲 4cm
self.body_motion([C.Torso_x, C.Torso_P], [0.02, math.pi/18]) # 躯干质心沿 x 轴向前 2cm ,绕 y 轴向前旋转 Π / 18 °
self.body_motion(C.Torso_z, 0.02) # 躯干质心沿 z 轴向上 2cm
self.body_motion([C.LArm_x, C.RArm_z], [0.04, 0.04]) # 左手沿 x 轴向前 4cm ,右手沿 z 轴向上 4cm
else:
rospy.logerr("ik_lib to initpose failed")

def rosShutdownHook(self):
self.reset()

if __name__ == "__main__":
rospy.init_node("IkAction")
IKA = IkAction()
rospy.on_shutdown(IKA.rosShutdownHook)
IKA.do_some_action()
  • 运行该脚本前,应该现在运行的终端加载工作空间的环境变量,再运行 Python 脚本

    source ~/robot_ros_application/catkin_ws/devel/setup.bash