本次blog实现订阅twist消息,并将之转换为go2所需的request消息,从而以ROS2自带键盘"cmd_vel"控制机器狗运动
1.实现思路:
要实现这个功能,重点在于数据的传递,需要订阅geometry_msgs/msg/Twist消息,然后解析geometry_msgs/msg/Twist消息并生成unitree_api/msg/Request消息,最后发布unitree_api/msg/Request消息即可。
2. 功能包创建
消息桥接可以使用c++或者python来实现,本文将逐个介绍。
现在打开终端,输入以下指令建立功能包:
- c++功能包:
cd unitree_go2_ws/src/base/
ros2 pkg create go2_twist_bridge --build-type ament_cmake --dependencies rclcpp geometry_msgs unitree_api --node-name twist_bridge
- python功能包:
cd unitree_go2_ws/src/base/
ros2 pkg create go2_twist_bridge_py --build-type ament_python --dependencies rclpy geometry_msgs unitree_api --node-name twist_bridge
创建完就可以开始编写代码了。
3. c++实现
1. 准备
实现之前要有json的文件,所以要去之前下载的unitree_ros2包里复制nlohmann包到go2_twist_bridge功能包中。所以需要到unitree_ros2/example/src/include目录下复制nlohmann包到unitree_go2_ws/src/base/go2_twist_bridge/include目录下。


随后在unitree_go2_ws/src/base/go2_twist_bridge/include目录下创建sport_model.hpp文件,用来存放定义常量和编号等。
sport_model.hpp文件编写代码如下:
#ifndef _SPORT_MODEL_
#define _SPORT_MODEL_
#include <iostream>
#pragma pack(1)
//1001:阻尼控制
const int32_t ROBOT_SPORT_API_ID_DAMP = 1001;
//1002:平衡站立,控制机器人进入平衡站立状态,保持直立和稳定
const int32_t ROBOT_SPORT_API_ID_BALANCESTAND = 1002;
//1003:停止运动,停止机器人的所有运动,使其立即静止
const int32_t ROBOT_SPORT_API_ID_STOPMOVE = 1003;
//1004:站立,控制机器人从其他姿势(坐下或躺下)恢复到站立姿势
const int32_t ROBOT_SPORT_API_ID_STANDUP = 1004;
//1005:站立下降,控制机器人从站立姿势转变为其他姿势(如坐下或躺下)
const int32_t ROBOT_SPORT_API_ID_STANDDOWN = 1005;
//1006:恢复站立,控制机器人在失去平衡后恢复到站立姿势
const int32_t ROBOT_SPORT_API_ID_RECOVERYSTAND = 1006;
//1007:欧拉角控制,用于调整机器人的姿态(如俯仰、横滚、偏航)
const int32_t ROBOT_SPORT_API_ID_EULER = 1007;
//1008:移动,控制机器人进行移动,可能是直线移动或转向
const int32_t ROBOT_SPORT_API_ID_MOVE = 1008;
//1009:坐下,控制机器人进入坐下姿势
const int32_t ROBOT_SPORT_API_ID_SIT = 1009;
//1010:从坐下恢复站立,控制机器人从坐下姿势恢复到站立姿势
const int32_t ROBOT_SPORT_API_ID_RISESIT = 1010;
//1011:切换步态,切换机器人的步态(如行走、跑步、爬行等)
const int32_t ROBOT_SPORT_API_ID_SWITCHGAIT = 1011;
//1012:触发,用于触发某个特定的动作或事件
const int32_t ROBOT_SPORT_API_ID_TRIGGER = 1012;
//1013:身体高度调整,调整机器人的身体高度
const int32_t ROBOT_SPORT_API_ID_BODYHEIGHT = 1013;
//1014:脚部抬起高度调整,调整机器人脚部的抬起高度
const int32_t ROBOT_SPORT_API_ID_FOOTTRAISEHEIGHT = 1014;
//1015:速度级别调整,调整机器人的运动速度级别
const int32_t ROBOT_SPORT_API_ID_SPEEDLEVEL = 1015;
//1016:打招呼,控制机器人执行"打招呼"的动作
const int32_t ROBOT_SPORT_API_ID_HELLO = 1016;
//1017:伸懒腰,控制机器人伸懒腰
const int32_t ROBOT_SPORT_API_ID_STRETCH = 1017;
//1018:轨迹跟随,控制机器人按照预定的轨迹进行移动
const int32_t ROBOT_SPORT_API_ID_TRAJECTORYEOLLOW = 1018;
//1019:连续步态,控制机器人进行连续的步态运动
const int32_t ROBOT_SPORT_API_ID_CONTINUOUSGAIT = 1019;
//1020:内容,可能用于控制机器人显示或执行某种内容(如播放视频或音频)
const int32_t ROBOT_SPORT_API_ID_CONTENT = 1020;
//1021:打滚,控制机器人执行"打滚"或"翻滚"动作
const int32_t ROBOT_SPORT_API_ID_WALLOW = 1021;
//1022:舞蹈1,控制机器人执行第一种舞蹈动作
const int32_t ROBOT_SPORT_API_ID_DANCE1 = 1022;
//1023:舞蹈2,控制机器人执行第二种舞蹈动作
const int32_t ROBOT_SPORT_API_ID_DANCE2 = 1023;
//1024:获取身体高度,获取机器人当前的身体高度
const int32_t ROBOT_SPORT_API_ID_GETBODYHEIGHT = 1024;
//1025:获取脚部抬起高度,获取机器人当前抬起脚部的高度
const int32_t ROBOT_SPORT_API_ID_GETFOOTRAISEHEIGHT = 1025;
//1026:获取速度级别,获取机器人当前的运动速度级别
const int32_t ROBOT_SPORT_API_ID_GETSPEEDLEVEL = 1026;
//1027:切换操纵杆,切换操纵杆的控制模式或功能
const int32_t ROBOT_SPORT_API_ID_SWITCHJOYSTICK = 1027;
//1028:姿态,控制机器人进入特定的姿势或姿态
const int32_t ROBOT_SPORT_API_ID_POSE = 1028;
//1029:刮擦,控制机器人进行“刮擦”动作
const int32_t ROBOT_SPORT_API_ID_SCRAPE = 1029;
//1030:前空翻,控制机器人执行“前空翻”动作
const int32_t ROBOT_SPORT_API_ID_FRONTFLIP = 1030;
//1031:前跳,控制机器人执行前跳动作
const int32_t ROBOT_SPORT_API_ID_FRONTJUMP = 1031;
//1032:前扑,控制机器人执行前扑动作
const int32_t ROBOT_SPORT_API_ID_FRONTPOUNCE = 1032;
#endif
定义了机器狗的各种动作及编号。
2.编写思路
随后在unitree_go2_ws/src/base/go2_twist_bridge/src目录下打开twist_bridge.cpp文件进行主程序的编写。
要实现消息桥接,就是要创建订阅方和发布方,这要用到ROS2的两个函数:create_publisher和create_subscription。
// 1.创建一个Request的发布对象
request_pub_ = this->create_publisher<unitree_api::msg::Request>("/api/sport/request",10);
// 2.创建Twist的订阅对象
twist_sub_ = this->create_subscription<geometry_msgs::msg::Twist>("cmd_vel",10,std::bind(&Twist_Bridge::twist_cb,this,_1));
在回调函数twist_cb中进行消息的桥接:
void twist_cb(const geometry_msgs::msg::Twist::SharedPtr twist){
// 3.在回调函数中实现消息的转换及发布
unitree_api::msg::Request request;
//转换
//获取 twist 消息的线速度和角速度
double x = twist->linear.x;
double y = twist->linear.y;
double th = twist->angular.z;
//默认平衡状态为平衡站立
auto api_id = ROBOT_SPORT_API_ID_BALANCESTAND;
if(x !=0 ||y !=0 || th!=0){
api_id = ROBOT_SPORT_API_ID_MOVE;
//设置参数----组织一个字符串样式速度指令
nlohmann::json js;
js["x"] = x;
js["y"] = y;
js["z"] = th;
request.parameter = js.dump();
RCLCPP_INFO(this->get_logger(),"current speed:%s",request.parameter.c_str());
}
request.header.identity.api_id = api_id;
request_pub_->publish(request);
}
然后再完成剩余程序的编写,是ros2的知识点了,不多概述。
最终的twist_bridge.cpp文件:
// 包含头文件
#include "rclcpp/rclcpp.hpp"
#include "unitree_api/msg/request.hpp"
#include "geometry_msgs/msg/twist.hpp"
#include "sport_model.hpp"
#include "nlohmann/json.hpp"
using namespace std::placeholders;
// 自定义节点类:
class Twist_Bridge: public rclcpp::Node {
public:
Twist_Bridge():Node("twist_bridge_node_cpp"){
RCLCPP_INFO(this->get_logger(),"TwistBridge创建!,可将geometry_msgs/msg/twist消息转换成unitree_api/msg/request消息");
// 1.创建一个Request的发布对象
request_pub_ = this->create_publisher<unitree_api::msg::Request>("/api/sport/request",10);
// 2.创建Twist的订阅对象
twist_sub_ = this->create_subscription<geometry_msgs::msg::Twist>("cmd_vel",10,std::bind(&Twist_Bridge::twist_cb,this,_1));
}
private:
rclcpp::Publisher<unitree_api::msg::Request>::SharedPtr request_pub_;
rclcpp::Subscription<geometry_msgs::msg::Twist>::SharedPtr twist_sub_;
void twist_cb(const geometry_msgs::msg::Twist::SharedPtr twist){
// 3.在回调函数中实现消息的转换及发布
unitree_api::msg::Request request;
//转换
//获取 twist 消息的线速度和角速度
double x = twist->linear.x;
double y = twist->linear.y;
double th = twist->angular.z;
//默认平衡状态为平衡站立
auto api_id = ROBOT_SPORT_API_ID_BALANCESTAND;
if(x !=0 ||y !=0 || th!=0){
api_id = ROBOT_SPORT_API_ID_MOVE;
//设置参数----组织一个字符串样式速度指令
nlohmann::json js;
js["x"] = x;
js["y"] = y;
js["z"] = th;
request.parameter = js.dump();
RCLCPP_INFO(this->get_logger(),"current speed:%s",request.parameter.c_str());
}
request.header.identity.api_id = api_id;
request_pub_->publish(request);
}
};
int main(int argc,char const *argv[])
{
// 2.初始化ros2客户端
rclcpp::init(argc,argv);
// 4.调用spin函数,传入自定义类对象指针
rclcpp::spin(std::make_shared<Twist_Bridge>());
// 5.释放资源
rclcpp::shutdown();
return 0;
}
3. 编译运行
输入以下指令编译运行桥接程序:
cd unitree_go2_ws
colcon build --packages-select go2_twist_bridge
. install/setup.bash
ros2 run go2_twist_bridge twist_bridge
然后Ctrl+Alt+T新建一个终端,运行
ros2 run teleop_twist_keyboard teleop_twist_keyboard

就可以实现用人ROS2内置的键盘来操控机器🐶运动了,效果最后展示。
3. python实现
1. 准备
先在~/unitree_go2_ws/src/base/go2_twist_bridge_py/go2_twist_bridge_py目录下创建sport_model.py文件,用来存放机器🐶对应的编号和动作。
sport_model.py文件编写代码如下:
#定义常量字典
ROBOT_SPORT_API_IDS = {
"DAMP":1001, #阻尼控制
"BALANCESTAND":1002, #平衡站立
"STOPMOVE":1003, #停止运动
"STANDUP":1004, #站立
"STANDDOWN":1005, #站立下降
"RECOVERYSTAND":1006, #恢复站立
"EULER":1007, #欧拉角控制
"MOVE":1008, #移动
"SIT":1009, #坐下
"RISESIT":1010, #从坐下恢复站立
"SWITCHGAIT":1011, #切换步态
"TRIGGER":1012, #触发
"BODYHEIGHT":1013, #身体高度调整
"FOOTTRAISEHEIGHT":1014, #脚步抬起高度调整
"SPEEDLEVEL":1015, #速度级别调整
"HELLO":1016, #打招呼
"STRETCH":1017, #伸懒腰
"TRAJECTORYEOLLOW":1018, #轨迹跟随
"CONTINUOUSGAIT":1019, #连续步态
"CONTENT":1020, #内容
"WALLOW":1021, #打滚
"DANCE1":1022, #舞蹈1
"DANCE2":1023, #舞蹈2
"GETBODYHEIGHT":1024, #获取身体高度
"GETFOOTRAISEHEIGHT":1025, #获取脚部抬起高度
"GETSPEEDLEVEL":1026, #获取速度级别
"SWITCHJOYSTICK":1027, #切换操纵杆
"POSE":1028, #姿态
"SCRAPE":1029, #刮擦
"FRONTFLIP":1030, #前空翻
"FRONTJUMP":1031, #前跳
"FRONTPOUNCE":1032, #前扑
}
定义了机器狗的各种动作及编号。
2.编写思路
随后在~/unitree_go2_ws/src/base/go2_twist_bridge_py/go2_twist_bridge_py目录下打开twist_bridge.py文件进行主程序的编写。
要实现消息桥接,就是要创建订阅方和发布方,这要用到ROS2的两个函数:create_publisher和create_subscription。
# 1.创建一个Request发布对象;
self.request_pub_ = self.create_publisher(Request,"/api/sport/request",10)
# 2.创建一个twist订阅对象;
self.twsit_sub_ = self.create_subscription(Twist,"cmd_vel",self.twist_to_request,10)
在回调函数twist_to_request中进行消息的桥接:
def twist_to_request(self,twist:Twist):
request = Request()
api_id = ROBOT_SPORT_API_IDS["BALANCESTAND"]
#转换,只要x,y线速度和z的角速度
x = twist.linear.x
y = twist.linear.y
z = twist.angular.z
#设置api_id
if x!=0 or y!=0 or z!=0:
api_id = ROBOT_SPORT_API_IDS["MOVE"]
#设置参数
js = {"x":x,"y":y,"z":z}
request.parameter = json.dumps(js)
request.header.identity.api_id = api_id
self.request_pub_.publish(request)
然后再完成剩余程序的编写。
最终的twist_bridge.py文件:
'''
需求:
流程:
需求:订阅twist消息,并将之转换为go2所需的request消息以控制机器狗运动
流程:
1.创建一个Request发布对象;
2.创建一个twist订阅对象;
3.在回调函数中实现消息的转换及发布
'''
#1.导包
import rclpy
from rclpy.node import Node
from .sport_model import ROBOT_SPORT_API_IDS
from geometry_msgs.msg import Twist
from unitree_api.msg import Request
import json
# 3.自定义节点类:
class Twist_Bridge(Node):
def __init__(self):
super().__init__("twist_bridge_node_py")
self.get_logger().info("TwistBridge创建!,可将twist消息转换成urequest消息")
# 1.创建一个Request发布对象;
self.request_pub_ = self.create_publisher(Request,"/api/sport/request",10)
# 2.创建一个twist订阅对象;
self.twsit_sub_ = self.create_subscription(Twist,"cmd_vel",self.twist_to_request,10)
def twist_to_request(self,twist:Twist):
request = Request()
api_id = ROBOT_SPORT_API_IDS["BALANCESTAND"]
#转换,只要x,y线速度和z的角速度
x = twist.linear.x
y = twist.linear.y
z = twist.angular.z
#设置api_id
if x!=0 or y!=0 or z!=0:
api_id = ROBOT_SPORT_API_IDS["MOVE"]
#设置参数
js = {"x":x,"y":y,"z":z}
request.parameter = json.dumps(js)
request.header.identity.api_id = api_id
self.request_pub_.publish(request)
def main():
# 2.初始化ros2
rclpy.init()
# 4.调用spin函数,传入自定义类对象指针
rclpy.spin(Twist_Bridge())
# 5.释放资源
rclpy.shutdown()
if __name__ == '__main__':
main()
对比不难发现,python语法比c++简洁。
3. 编译运行
输入以下指令编译运行桥接程序:
cd unitree_go2_ws
colcon build --packages-select go2_twist_bridge_py
. install/setup.bash
ros2 run go2_twist_bridge_py twist_bridge
然后Ctrl+Alt+T新建一个终端,运行
ros2 run teleop_twist_keyboard teleop_twist_keyboard

就可以实现用ROS2内置的键盘来操控机器🐶运动了,效果也最后展示。
4. 结果展示
桥接消息的机器狗
下午阳光明媚,机器🐶身上缕缕金丝,用ROS2内置键盘操控,比较放荡形骸哈哈哈哈,这和第2次blog用自制键盘操控实现的效果其实差不多。
这篇blog就这样了,下次再见!!!

1万+

被折叠的 条评论
为什么被折叠?



