ROS2 lowstate interface and corresponding example added.

This commit is contained in:
Rooholla-KhorramBakht 2024-02-08 22:02:42 -05:00
parent f163798f20
commit c57c81290c
7 changed files with 224 additions and 30 deletions

2
.gitignore vendored
View File

@ -15,7 +15,7 @@ downloads/
eggs/ eggs/
.eggs/ .eggs/
# lib/ # lib/
lib64/ # lib64/
parts/ parts/
sdist/ sdist/
var/ var/

View File

@ -12,7 +12,7 @@ from rclpy.executors import MultiThreadedExecutor
from geometry_msgs.msg import TransformStamped from geometry_msgs.msg import TransformStamped
from Go2Py.joy import xKeySwitch, xRockerBtn from Go2Py.joy import xKeySwitch, xRockerBtn
from geometry_msgs.msg import TwistStamped from geometry_msgs.msg import TwistStamped
from unitree_go.msg import LowState, LowCmd from unitree_go.msg import LowState, Go2pyLowCmd
from nav_msgs.msg import Odometry from nav_msgs.msg import Odometry
@ -77,7 +77,7 @@ class GO2Real(Node):
self.lowstate_subscriber = self.create_subscription( self.lowstate_subscriber = self.create_subscription(
LowState, self.lowstate_topic, self.lowstate_callback, 1 LowState, self.lowstate_topic, self.lowstate_callback, 1
) )
self.lowcmd_publisher = self.create_publisher(LowCmd, self.lowcmd_topic, 1) self.lowcmd_publisher = self.create_publisher(Go2pyLowCmd, self.lowcmd_topic, 1)
self.odometry_subscriber = self.create_subscription( self.odometry_subscriber = self.create_subscription(
Odometry, "/utlidar/robot_odom", self.odom_callback, 1 Odometry, "/utlidar/robot_odom", self.odom_callback, 1
@ -95,8 +95,8 @@ class GO2Real(Node):
self.ωz_max = ωz_max self.ωz_max = ωz_max
self.ωz_min = -ωz_max self.ωz_min = -ωz_max
self.running = True self.running = True
self.setCommands = {'lowstate':self.setCommandsLow, self.setCommands = {'lowlevel':self.setCommandsLow,
'highstate':self.setCommandsHigh}[self.mode] 'highlevel':self.setCommandsHigh}[self.mode]
def lowstate_callback(self, msg): def lowstate_callback(self, msg):
""" """
@ -203,14 +203,14 @@ class GO2Real(Node):
self.highcmd.twist.angular.z = _ω_z self.highcmd.twist.angular.z = _ω_z
self.highcmd_publisher.publish(self.highcmd) self.highcmd_publisher.publish(self.highcmd)
def setCommandsLow(q, dq, kp, kd, tau_ff): def setCommandsLow(self, q, dq, kp, kd, tau_ff):
assert q.size == qd.size == kp.size == kd.size == tau_ff.size == 12, "q, dq, kp, kd, tau_ff should have size 12" assert q.size == dq.size == kp.size == kd.size == tau_ff.size == 12, "q, dq, kp, kd, tau_ff should have size 12"
lowcmd = LowCmd() lowcmd = Go2pyLowCmd()
lowcmd.motor_cmd.q = q.tolist() lowcmd.q = q.tolist()
lowcmd.motor_cmd.dq = dq.tolist() lowcmd.dq = dq.tolist()
lowcmd.motor_cmd.kp = kp.tolist() lowcmd.kp = kp.tolist()
lowcmd.motor_cmd.kd = kd.tolist() lowcmd.kd = kd.tolist()
lowcmd.motor_cmd.tau_ff = tau_ff.tolist() lowcmd.tau = tau_ff.tolist()
self.lowcmd_publisher.publish(lowcmd) self.lowcmd_publisher.publish(lowcmd)
def close(self): def close(self):

View File

@ -1,6 +1,7 @@
#include "unitree_go/msg/low_state.hpp" #include "unitree_go/msg/low_state.hpp"
#include "unitree_go/msg/imu_state.hpp" #include "unitree_go/msg/imu_state.hpp"
#include "unitree_go/msg/motor_state.hpp" #include "unitree_go/msg/motor_state.hpp"
#include "unitree_go/msg/go2py_low_cmd.hpp"
#include "unitree_go/msg/low_cmd.hpp" #include "unitree_go/msg/low_cmd.hpp"
#include "unitree_go/msg/motor_cmd.hpp" #include "unitree_go/msg/motor_cmd.hpp"
@ -44,7 +45,7 @@ class Custom: public rclcpp::Node
lowstate_suber = this->create_subscription<unitree_go::msg::LowState>( lowstate_suber = this->create_subscription<unitree_go::msg::LowState>(
"lowstate", 1, std::bind(&Custom::lowstate_callback, this, std::placeholders::_1)); "lowstate", 1, std::bind(&Custom::lowstate_callback, this, std::placeholders::_1));
lowcmd_suber = this->create_subscription<unitree_go::msg::LowCmd>( lowcmd_suber = this->create_subscription<unitree_go::msg::Go2pyLowCmd>(
"/go2/lowcmd", 1, std::bind(&Custom::lowcmd_callback, this, std::placeholders::_1)); "/go2/lowcmd", 1, std::bind(&Custom::lowcmd_callback, this, std::placeholders::_1));
lowcmd_puber = this->create_publisher<unitree_go::msg::LowCmd>("/lowcmd", 10); lowcmd_puber = this->create_publisher<unitree_go::msg::LowCmd>("/lowcmd", 10);
@ -71,9 +72,9 @@ class Custom: public rclcpp::Node
// Lowlevel interface // Lowlevel interface
void lowstate_callback(unitree_go::msg::LowState::SharedPtr data); void lowstate_callback(unitree_go::msg::LowState::SharedPtr data);
void lowcmd_callback(unitree_go::msg::LowCmd::SharedPtr data); void lowcmd_callback(unitree_go::msg::Go2pyLowCmd::SharedPtr data);
rclcpp::Subscription<unitree_go::msg::LowState>::SharedPtr lowstate_suber; rclcpp::Subscription<unitree_go::msg::LowState>::SharedPtr lowstate_suber;
rclcpp::Subscription<unitree_go::msg::LowCmd>::SharedPtr lowcmd_suber; rclcpp::Subscription<unitree_go::msg::Go2pyLowCmd>::SharedPtr lowcmd_suber;
// A struct to store the highlevel states for later use // A struct to store the highlevel states for later use
rclcpp::Publisher<unitree_go::msg::LowCmd>::SharedPtr lowcmd_puber; rclcpp::Publisher<unitree_go::msg::LowCmd>::SharedPtr lowcmd_puber;
unitree_go::msg::LowCmd lowcmd_msg; unitree_go::msg::LowCmd lowcmd_msg;
@ -231,15 +232,15 @@ void Custom::twistCmdCallback(const geometry_msgs::msg::TwistStamped::SharedPtr
highreq_puber->publish(highreq); highreq_puber->publish(highreq);
} }
void Custom::lowcmd_callback(unitree_go::msg::LowCmd::SharedPtr data) void Custom::lowcmd_callback(unitree_go::msg::Go2pyLowCmd::SharedPtr data)
{ {
for(int i=0; i<12; i++) for(int i=0; i<12; i++)
{ {
lowcmd_msg.motor_cmd[i].q = data->motor_cmd[i].q; // Taregt angular(rad) lowcmd_msg.motor_cmd[i].q = data->q[i]; // Taregt angular(rad)
lowcmd_msg.motor_cmd[i].kp = data->motor_cmd[i].kp; // Poinstion(rad) control kp gain lowcmd_msg.motor_cmd[i].kp = data->kp[i]; // Poinstion(rad) control kp gain
lowcmd_msg.motor_cmd[i].dq = data->motor_cmd[i].dq; // Taregt angular velocity(rad/ss) lowcmd_msg.motor_cmd[i].dq = data->dq[i]; // Taregt angular velocity(rad/ss)
lowcmd_msg.motor_cmd[i].kd = data->motor_cmd[i].kd; // Poinstion(rad) control kd gain lowcmd_msg.motor_cmd[i].kd = data->kd[i]; // Poinstion(rad) control kd gain
lowcmd_msg.motor_cmd[i].tau = data->motor_cmd[i].tau; // Feedforward toque 1N.m lowcmd_msg.motor_cmd[i].tau = data->tau[i]; // Feedforward toque 1N.m
get_crc(lowcmd_msg); //Compute the CRC and load it into the message get_crc(lowcmd_msg); //Compute the CRC and load it into the message
lowcmd_puber->publish(lowcmd_msg); //Publish lowcmd message lowcmd_puber->publish(lowcmd_msg); //Publish lowcmd message
} }

View File

@ -48,6 +48,7 @@ rosidl_generate_interfaces(${PROJECT_NAME}
"msg/UwbState.msg" "msg/UwbState.msg"
"msg/UwbSwitch.msg" "msg/UwbSwitch.msg"
"msg/WirelessController.msg" "msg/WirelessController.msg"
"msg/Go2pyLowCmd.msg"
DEPENDENCIES geometry_msgs DEPENDENCIES geometry_msgs
) )

View File

@ -0,0 +1,5 @@
float32[12] q
float32[12] dq
float32[12] kp
float32[12] kd
float32[12] tau

View File

@ -12,9 +12,18 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 2,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"pygame 2.5.2 (SDL 2.28.2, Python 3.8.18)\n",
"Hello from the pygame community. https://www.pygame.org/contribute.html\n"
]
}
],
"source": [ "source": [
"from Go2Py.joy import Logitech3DPro\n", "from Go2Py.joy import Logitech3DPro\n",
"# joy = Logitech3DPro(joy_id=0) " "# joy = Logitech3DPro(joy_id=0) "
@ -31,14 +40,23 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 1,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"pygame 2.5.2 (SDL 2.28.2, Python 3.8.18)\n",
"Hello from the pygame community. https://www.pygame.org/contribute.html\n"
]
}
],
"source": [ "source": [
"from Go2Py.robot.interface.ros2 import GO2Real, ros2_init, ROS2ExecutorManager\n", "from Go2Py.robot.interface.ros2 import GO2Real, ros2_init, ROS2ExecutorManager\n",
"import time\n", "import time\n",
"ros2_init()\n", "ros2_init()\n",
"robot = GO2Real()\n", "robot = GO2Real(mode='lowlevel')\n",
"ros2_exec_manager = ROS2ExecutorManager()\n", "ros2_exec_manager = ROS2ExecutorManager()\n",
"ros2_exec_manager.add_node(robot)\n", "ros2_exec_manager.add_node(robot)\n",
"ros2_exec_manager.start()" "ros2_exec_manager.start()"
@ -71,11 +89,24 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 17,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [
{
"data": {
"text/plain": [
"{'stamp_nanosec': 1707447716.9414463,\n",
" 'position': array([0.01655228, 0.00701126, 0.30197507]),\n",
" 'orientation': array([-0.00573182, 0.0074235 , 0.08994701, 0.99590236])}"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [ "source": [
"robot.getJointStates()" "robot.getOdometry()"
] ]
} }
], ],

View File

@ -0,0 +1,156 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Joystick Control\n",
"As the first step after successfully communicating with the robot's onboard controller, we use a USB joystick (a Logitech Extreme 3D Pro) to command the robot with desired $x$, $y$, and $\\Psi$ velocities.\n",
"\n",
"First, use the Pygame-based joystick class provided with B1Py to connect to the joystick:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"pygame 2.5.2 (SDL 2.28.2, Python 3.8.18)\n",
"Hello from the pygame community. https://www.pygame.org/contribute.html\n"
]
}
],
"source": [
"from Go2Py.joy import Logitech3DPro\n",
"# joy = Logitech3DPro(joy_id=0) "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"***Note:*** Initially when you instantiate the joystick, the class performs an offset calibration to maker sure the neutral configuration of the device reads zero. It is important to leave the device untouched during the couple of seconds after calling the cell above. The prompts tells you when you can continue. \n",
"\n",
"Then as explained in [hardware interface documentation](unitree_locomotion_controller_interface.ipynb), instantiate and communication link to high-level controller on the robot. Note that the robot should be standing and ready to walk before we can control it. Furthermore, the the highlevel node in b1py_node ROS2 package (in `deploy/ros2_ws`) should be launched on the robot. "
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"pygame 2.5.2 (SDL 2.28.2, Python 3.8.18)\n",
"Hello from the pygame community. https://www.pygame.org/contribute.html\n"
]
}
],
"source": [
"from Go2Py.robot.interface.ros2 import GO2Real, ros2_init, ROS2ExecutorManager\n",
"import time\n",
"ros2_init()\n",
"robot = GO2Real(mode='lowlevel')\n",
"ros2_exec_manager = ROS2ExecutorManager()\n",
"ros2_exec_manager.add_node(robot)\n",
"ros2_exec_manager.start()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, read the joystick values and send them to the robot in a loop:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"Duration = 20\n",
"N = Duration//0.01\n",
"for i in range(500):\n",
" cmd = joy.readAnalog()\n",
" vx = cmd['x']\n",
" vy = cmd['y']\n",
" w = cmd['z'] / 2\n",
" body_height = cmd['aux'] / 10\n",
" robot.setCommands(vx,vy,w, bodyHeight=body_height)\n",
" time.sleep(0.01)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import time\n",
"for i in range(10000):\n",
" q = np.zeros(12) \n",
" dq = np.zeros(12)\n",
" kp = np.zeros(12)\n",
" kd = np.zeros(12)\n",
" tau = np.zeros(12)\n",
" tau[0] = -0.8\n",
" robot.setCommands(q, dq, kp, kd, tau)\n",
" time.sleep(0.01) "
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'q': array([-0.06477112, 1.24850821, -2.8002429 , 0.04539341, 1.25765204,\n",
" -2.78191853, -0.34121001, 1.27112007, -2.79908991, 0.34284496,\n",
" 1.26930332, -2.8161664 ]),\n",
" 'dq': array([ 0. , 0.01937762, -0.00606604, -0.01162657, 0. ,\n",
" 0.01819811, 0.01937762, 0. , 0.03033019, -0.05813286,\n",
" 0.00775105, 0.00808805])}"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"robot.getJointStates()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.18"
}
},
"nbformat": 4,
"nbformat_minor": 4
}