Ported tutorials to ROS 2 Galactic - first batchgalactic
@ -1,57 +1,25 @@ | |||
# Getting Started | |||
## Ubuntu | |||
## Installing Ubuntu 20.04 with ROS 2 Galactic on Stretch | |||
Hello Robot utilizes Ubuntu, an open source Linux operating system, for the Stretch RE1 platform. If you are unfamiliar with the operating system, we encourage you to review a [tutorial](https://ubuntu.com/tutorials/command-line-for-beginners#1-overview) provided by Ubuntu. Additionally, the Linux command line, BASH, is used to execute commands and is needed to run ROS on the Stretch robot. Here is a [tutorial](https://ryanstutorials.net/linuxtutorial/) on getting started with BASH. | |||
Hello Robot utilizes Ubuntu, an open-source Linux operating system, for the Stretch RE1 platform. If you are unfamiliar with the operating system, we encourage you to review a [tutorial](https://ubuntu.com/tutorials/command-line-for-beginners#1-overview) provided by Ubuntu. Additionally, the Linux command line, BASH, is used to execute commands and is needed to run ROS on the Stretch robot. Here is a [tutorial](https://ryanstutorials.net/linuxtutorial/) on getting started with BASH. | |||
<!-- TODO: Change the installation instructions link below --> | |||
Instructions on installing Ubuntu 20.04 with ROS Noetic and ROS 2 Galactic can be found in our open source [installation guide](https://github.com/hello-robot/stretch_ros/blob/dev/noetic/install_noetic.md). Following these steps should create a separate Ubuntu 20.04 partition with an ament worskspace created in the home directory. | |||
## Installing Noetic on Stretch | |||
Instructions on installing Noetic can be found in our open-source [installation guide](https://github.com/hello-robot/stretch_ros/blob/dev/noetic/install_noetic.md). Then create a catkin workspace for your ROS packages. Here is an [installation guide for creating a workspace](http://wiki.ros.org/catkin/Tutorials/create_a_workspace). Once your system is set up, clone the [stretch_ros_tutorials](https://github.com/hello-sanchez/stretch_ros_tutorials.git) to your workspace and build the package in your workspace. This can be done by copying the commands below and pasting them into your terminal. | |||
## ROS 2 Tutorials Setup on Local Computer | |||
Once your system is setup, clone the [stretch_ros_tutorials](https://github.com/hello-sanchez/stretch_ros_tutorials.git) to the src directory of the ament workspace, then build the packages. | |||
``` | |||
cd ~/catkin_ws/src | |||
cd ~/ament_ws/src | |||
<!-- TODO: Change the link below --> | |||
git clone https://github.com/hello-sanchez/stretch_ros_tutorials.git | |||
cd ~/catkin_ws | |||
catkin_make | |||
cd ~/ament_ws | |||
colcon build | |||
``` | |||
## ROS Setup on Local Computer | |||
Hello Robot is currently running Stretch on Ubuntu 20.04 and ROS Noetic. To begin the setup, start with [installing Ubuntu desktop](https://ubuntu.com/tutorials/install-ubuntu-desktop#1-overview) on your local machine. Then follow the [installation guide for ROS Noetic](http://wiki.ros.org/noetic/Installation/Ubuntu) on your system. | |||
Currently, the Realsense2_description package isn't installed by rosdep and requires a user to install the package manually. Run the following command in your terminal | |||
``` | |||
sudo apt-get install ros-noetic-realsense2-camera | |||
``` | |||
After your system is setup, clone the [stretch_ros](https://github.com/hello-robot/stretch_ros.git), [stretch_ros_tutorials](https://github.com/hello-sanchez/stretch_ros_tutorials.git), and [realsense_gazebo_plugin packages]( https://github.com/pal-robotics/realsense_gazebo_plugin) to your **src** folder in your preferred workspace. | |||
``` | |||
cd ~/catkin_ws/src | |||
git clone https://github.com/hello-robot/stretch_ros | |||
git clone https://github.com/pal-robotics/realsense_gazebo_plugin | |||
git clone https://github.com/hello-sanchez/stretch_ros_tutorials.git | |||
Then source your workspace with the following command | |||
``` | |||
Change the directory to that of your catkin workspace and install system dependencies of the ROS packages. Then build your workspace. | |||
source ~/ament_ws/install/setup.bash" | |||
``` | |||
cd ~/catkin_ws | |||
rosdep install --from-paths src --ignore-src -r -y | |||
catkin_make | |||
``` | |||
Once `caktin_make` has finished compiling,source your workspace and **.bashrc** file | |||
``` | |||
echo "source /home/USER_NAME/catkin_ws/devel/setup.bash" | |||
source ~/.bashrc | |||
``` | |||
## RoboMaker | |||
![image](images/aws-robomaker.png) | |||
If you cannot dual boot and install ubuntu on your local machine, an alternative is to use [AWS RoboMaker](https://aws.amazon.com/robomaker/). AWS RoboMaker extends the ROS framework with cloud services. The service provides a robotics simulation service, allowing for testing the Stretch RE1 platform. If you are a first-time user of AWS RoboMaker, follow the [guide here](https://github.com/aws-robotics/aws-robomaker-robotics-curriculum/blob/main/getting-started-with-aws-robomaker/_modules/mod-2a-aws.md) to get up and running with the service. | |||
**Next Tutorial:** [Gazebo Basics](gazebo_basics.md) |
@ -0,0 +1,24 @@ | |||
<?xml version="1.0"?> | |||
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?> | |||
<package format="3"> | |||
<name>stretch_ros_tutorials</name> | |||
<version>0.0.0</version> | |||
<description>This package contains a set of ROS tutorials for Stretch beginners</description> | |||
<maintainer email="cdesai@hello-robot.com">Chintan Desai</maintainer> | |||
<license>Apache License 2.0</license> | |||
<exec_depend>rclpy</exec_depend> | |||
<exec_depend>std_msgs</exec_depend> | |||
<exec_depend>geometry_msgs</exec_depend> | |||
<exec_depend>control_msgs</exec_depend> | |||
<exec_depend>trajectory_msgs</exec_depend> | |||
<test_depend>ament_copyright</test_depend> | |||
<test_depend>ament_flake8</test_depend> | |||
<test_depend>ament_pep257</test_depend> | |||
<test_depend>python3-pytest</test_depend> | |||
<export> | |||
<build_type>ament_python</build_type> | |||
</export> | |||
</package> |
@ -0,0 +1,4 @@ | |||
[develop] | |||
script_dir=$base/lib/stretch_ros_tutorials | |||
[install] | |||
install_scripts=$base/lib/stretch_ros_tutorials |
@ -0,0 +1,33 @@ | |||
from setuptools import setup | |||
package_name = 'stretch_ros_tutorials' | |||
setup( | |||
name=package_name, | |||
version='0.0.0', | |||
packages=[package_name], | |||
data_files=[ | |||
('share/ament_index/resource_index/packages', | |||
['resource/' + package_name]), | |||
('share/' + package_name, ['package.xml']), | |||
], | |||
install_requires=['setuptools'], | |||
zip_safe=True, | |||
maintainer='Chintan Desai', | |||
maintainer_email='cdesai@hello-robot.com', | |||
description='This package contains a set of ROS tutorials for Stretch beginners', | |||
license='Apache License 2.0', | |||
tests_require=['pytest'], | |||
entry_points={ | |||
'console_scripts': [ | |||
'stow_command = stretch_ros_tutorials.stow_command:main', | |||
'multipoint_command = stretch_ros_tutorials.multipoint_command:main', | |||
'marker = stretch_ros_tutorials.marker:main', | |||
'tf_broadcaster = stretch_ros_tutorials.tf_broadcaster:main', | |||
'tf_listener = stretch_ros_tutorials.tf_listener:main', | |||
'scan_filter = stretch_ros_tutorials.scan_filter:main', | |||
'avoider = stretch_ros_tutorials.avoider:main', | |||
'move = stretch_ros_tutorials.move:main', | |||
], | |||
}, | |||
) |
@ -1,74 +0,0 @@ | |||
#!/usr/bin/env python | |||
# This node will publish a spherical marker above the current robot position, as | |||
# it drives around the world. | |||
# Import rospy and sys | |||
import rospy | |||
# Import the Marker message type from the visualization_msgs package. | |||
from visualization_msgs.msg import Marker | |||
class Balloon(): | |||
def __init__(self): | |||
# Set up a publisher. We're going to publish on a topic called balloon. | |||
self.publisher = rospy.Publisher('balloon', Marker, queue_size=10) | |||
# Create a marker. Markers of all shapes share a common type. | |||
self.marker = Marker() | |||
# Set the frame ID and type. The frame ID is the frame in which the position of the marker | |||
# is specified. The type is the shape of the marker, detailed on the wiki page. | |||
self.marker.header.frame_id = '/base_link' | |||
self.marker.header.stamp = rospy.Time() | |||
self.marker.type = self.marker.SPHERE | |||
# Each marker has a unique ID number. If you have more than one marker that | |||
# you want displayed at a given time, then each needs to have a unique ID | |||
# number.If you publish a new marker with the same ID number and an existing | |||
# marker, it will replace the existing marker with that ID number. | |||
self.marker.id = 0 | |||
# Set the action. We can add, delete, or modify markers. | |||
self.marker.action = self.marker.ADD | |||
# These are the size parameters for the marker. These will vary by marker type | |||
self.marker.scale.x = 0.5 | |||
self.marker.scale.y = 0.5 | |||
self.marker.scale.z = 0.5 | |||
# Color, as an RGB triple, from 0 to 1. | |||
self.marker.color.r = 1.0 | |||
self.marker.color.g = 0.0 | |||
self.marker.color.b = 0.0 | |||
# Alpha value, from 0 (invisible) to 1 (opaque). If you don't set this and | |||
# it defaults to zero, then your marker will be invisible. | |||
self.marker.color.a = 1.0 | |||
# Specify the pose of the marker. Since spheres are rotationally invarient, | |||
# we're only going to specify the positional elements. As usual, these are | |||
# in the coordinate frame named in frame_id. In this case, the position | |||
# will always be directly above the robot, and will move with it. | |||
self.marker.pose.position.x = 0.0 | |||
self.marker.pose.position.y = 0.0 | |||
self.marker.pose.position.z = 2.0 | |||
def publish_marker(self): | |||
# publisher the marker | |||
self.publisher.publish(self.marker) | |||
if __name__ == '__main__': | |||
# Initialize the node, as usual | |||
rospy.init_node('marker') | |||
ballon = Balloon() | |||
# Set a rate. | |||
rate = rospy.Rate(10) | |||
# Publish the marker at 10Hz. | |||
while not rospy.is_shutdown(): | |||
ballon.publish_marker() | |||
rate.sleep() |
@ -1,57 +0,0 @@ | |||
#!/usr/bin/env python | |||
# Every python controller needs these lines | |||
import rospy | |||
# The Twist message is used to send velocities to the robot. | |||
from geometry_msgs.msg import Twist | |||
class Move: | |||
def __init__(self): | |||
# Setup a publisher that will send the velocity commands for the Stretch | |||
# This will publish on a topic called "/stretch/cmd_vel" with a message type Twist. | |||
self.pub = rospy.Publisher('/stretch/cmd_vel', Twist, queue_size=1) #/stretch_diff_drive_controller/cmd_vel for gazebo | |||
def move_forward(self): | |||
# Make a Twist message. We're going to set all of the elements, since we | |||
# can't depend on them defaulting to safe values. | |||
command = Twist() | |||
# A Twist has three linear velocities (in meters per second), along each of the axes. | |||
# For Stretch, it will only pay attention to the x velocity, since it can't | |||
# directly move in the y direction or the z direction. | |||
command.linear.x = 0.1 | |||
command.linear.y = 0.0 | |||
command.linear.z = 0.0 | |||
# A Twist also has three rotational velocities (in radians per second). | |||
# The Stretch will only respond to rotations around the z (vertical) axis. | |||
command.angular.x = 0.0 | |||
command.angular.y = 0.0 | |||
command.angular.z = 0.0 | |||
# Publish the Twist commands | |||
self.pub.publish(command) | |||
if __name__ == '__main__': | |||
# Initialize the node, and call it "move". | |||
rospy.init_node('move') | |||
# Setup Move class to base_motion | |||
base_motion = Move() | |||
# Rate allows us to control the (approximate) rate at which we publish things. | |||
# For this example, we want to publish at 10Hz. | |||
rate = rospy.Rate(10) | |||
# This will loop until ROS shuts down the node. This can be done on the | |||
# command line with a ctrl-C, or automatically from roslaunch. | |||
while not rospy.is_shutdown(): | |||
# Run the move_foward function in the Move class | |||
base_motion.move_forward() | |||
# Do an idle wait to control the publish rate. If we don't control the | |||
# rate, the node will publish as fast as it can, and consume all of the | |||
# available CPU resources. This will also add a lot of network traffic, | |||
# possibly slowing down other things. | |||
rate.sleep() |
@ -1,98 +0,0 @@ | |||
#!/usr/bin/env python3 | |||
# Every python controller needs these lines | |||
import rospy | |||
import time | |||
# Import the FollowJointTrajectoryGoal from the control_msgs.msg package to | |||
# control the Stretch robot. | |||
from control_msgs.msg import FollowJointTrajectoryGoal | |||
# Import JointTrajectoryPoint from the trajectory_msgs package to define | |||
# robot trajectories. | |||
from trajectory_msgs.msg import JointTrajectoryPoint | |||
# Import hello_misc script for handling trajecotry goals with an action client. | |||
import hello_helpers.hello_misc as hm | |||
class MultiPointCommand(hm.HelloNode): | |||
# Initialize the inhereted hm.Hellonode class. | |||
def __init__(self): | |||
hm.HelloNode.__init__(self) | |||
def issue_multipoint_command(self): | |||
# Set point0 as a JointTrajectoryPoint(). | |||
point0 = JointTrajectoryPoint() | |||
# Provide desired positions of lift, wrist extension, and yaw of | |||
# the wrist (in meters). | |||
point0.positions = [0.2, 0.0, 3.4] | |||
# Provide desired velocity of the lift (m/s), wrist extension (m/s), | |||
# and wrist yaw (rad/s). | |||
# IMPORTANT NOTE: The lift and wrist extension can only go up to 0.2 m/s! | |||
point0.velocities = [0.2, 0.2, 2.5] | |||
# Provide desired velocity of the lift (m/s^2), wrist extension (m/s^2), | |||
# and wrist yaw (rad/s^2). | |||
point0.accelerations = [1.0, 1.0, 3.5] | |||
# Set positions for the following 5 trajectory points. | |||
# IMPORTANT NOTE: If you do not provide any velocities or accelerations for the lift | |||
# or wrist extension, then they go to their default values. However, the | |||
# Velocity and Acceleration of the wrist yaw will stay the same from the | |||
# previous value unless updated. | |||
point1 = JointTrajectoryPoint() | |||
point1.positions = [0.3, 0.1, 2.0] | |||
point2 = JointTrajectoryPoint() | |||
point2.positions = [0.5, 0.2, -1.0] | |||
point3 = JointTrajectoryPoint() | |||
point3.positions = [0.6, 0.3, 0.0] | |||
point4 = JointTrajectoryPoint() | |||
point4.positions = [0.8, 0.2, 1.0] | |||
point5 = JointTrajectoryPoint() | |||
point5.positions = [0.5, 0.1, 0.0] | |||
# Set trajectory_goal as a FollowJointTrajectoryGoal and define | |||
# the joint names as a list. | |||
trajectory_goal = FollowJointTrajectoryGoal() | |||
trajectory_goal.trajectory.joint_names = ['joint_lift', 'wrist_extension', 'joint_wrist_yaw'] | |||
# Then trajectory_goal.trajectory.points is defined by a list of the joint | |||
# trajectory points | |||
trajectory_goal.trajectory.points = [point0, point1, point2, point3, point4, point5] | |||
# Specify the coordinate frame that we want (base_link) and set the time to be now. | |||
trajectory_goal.trajectory.header.stamp = rospy.Time(0.0) | |||
trajectory_goal.trajectory.header.frame_id = 'base_link' | |||
# Make the action call and send the goal. The last line of code waits | |||
# for the result before it exits the python script. | |||
self.trajectory_client.send_goal(trajectory_goal) | |||
rospy.loginfo('Sent stow goal = {0}'.format(trajectory_goal)) | |||
self.trajectory_client.wait_for_result() | |||
# Create a funcion, main(), to do all of the setup the hm.HelloNode class | |||
# and issue the stow command. | |||
def main(self): | |||
# The arguments of the main function of the hm.HelloNode class are the | |||
# node_name, node topic namespace, and boolean (default value is true). | |||
hm.HelloNode.main(self, 'multipoint_command', 'multipoint_command', wait_for_first_pointcloud=False) | |||
rospy.loginfo('issuing multipoint command...') | |||
self.issue_multipoint_command() | |||
time.sleep(2) | |||
if __name__ == '__main__': | |||
try: | |||
# Initialize the MultiPointCommand() class and set it to node and run the | |||
# main() function. | |||
node = MultiPointCommand() | |||
node.main() | |||
except KeyboardInterrupt: | |||
rospy.loginfo('interrupt received, so shutting down') |
@ -1,70 +0,0 @@ | |||
#!/usr/bin/env python3 | |||
# Every python controller needs these lines | |||
import rospy | |||
import time | |||
# Import the FollowJointTrajectoryGoal from the control_msgs.msg package to | |||
# control the Stretch robot. | |||
from control_msgs.msg import FollowJointTrajectoryGoal | |||
# Import JointTrajectoryPoint from the trajectory_msgs package to define | |||
# robot trajectories. | |||
from trajectory_msgs.msg import JointTrajectoryPoint | |||
# Import hello_misc script for handling trajecotry goals with an action client. | |||
import hello_helpers.hello_misc as hm | |||
class StowCommand(hm.HelloNode): | |||
# Initialize the inhereted hm.Hellonode class. | |||
def __init__(self): | |||
hm.HelloNode.__init__(self) | |||
def issue_stow_command(self): | |||
# Set stow_point as a JointTrajectoryPoint(). | |||
stow_point = JointTrajectoryPoint() | |||
stow_point.time_from_start = rospy.Duration(0.000) | |||
# Provide desired positions of lift, wrist extension, and yaw of | |||
# the wrist (in meters). | |||
stow_point.positions = [0.2, 0.0, 3.4] | |||
# Set trajectory_goal as a FollowJointTrajectoryGoal and define | |||
# the joint names as a list. | |||
trajectory_goal = FollowJointTrajectoryGoal() | |||
trajectory_goal.trajectory.joint_names = ['joint_lift', 'wrist_extension', 'joint_wrist_yaw'] | |||
# Then trajectory_goal.trajectory.points is defined by the positions | |||
# set in stow_point | |||
trajectory_goal.trajectory.points = [stow_point] | |||
# Specify the coordinate frame that we want (base_link) and set the time to be now. | |||
trajectory_goal.trajectory.header.stamp = rospy.Time(0.0) | |||
trajectory_goal.trajectory.header.frame_id = 'base_link' | |||
# Make the action call and send the goal. The last line of code waits | |||
# for the result before it exits the python script. | |||
self.trajectory_client.send_goal(trajectory_goal) | |||
rospy.loginfo('Sent stow goal = {0}'.format(trajectory_goal)) | |||
self.trajectory_client.wait_for_result() | |||
# Create a funcion, main(), to do all of the setup the hm.HelloNode class | |||
# and issue the stow command. | |||
def main(self): | |||
# The arguments of the main function of the hm.HelloNode class are the | |||
# node_name, node topic namespace, and boolean (default value is true). | |||
hm.HelloNode.main(self, 'stow_command', 'stow_command', wait_for_first_pointcloud=False) | |||
rospy.loginfo('stowing...') | |||
self.issue_stow_command() | |||
time.sleep(2) | |||
if __name__ == '__main__': | |||
try: | |||
# Initialize the StowCommand() class and set it to node and run the | |||
# main() function. | |||
node = StowCommand() | |||
node.main() | |||
except KeyboardInterrupt: | |||
rospy.loginfo('interrupt received, so shutting down') |
@ -0,0 +1,56 @@ | |||
import rclpy | |||
import math | |||
import numpy as np | |||
from rclpy.node import Node | |||
class ForwardKinematics(): | |||
def __init__(self, lift_pos, arm_pos, wrist_pos): | |||
# Get joint values as node parameters that can be set from CLI | |||
self.a1 = 0 | |||
self.alpha1 = 0 | |||
self.d1 = 0.2 | |||
self.theta1 = 0 | |||
self.a2 = 0 | |||
self.alpha2 = 0 | |||
self.d2 = lift_pos | |||
self.theta2 = 0 | |||
self.a3 = 0 | |||
self.alpha3 = -1.5708 | |||
self.d3 = 0.5 + arm_pos | |||
self.theta3 = -wrist_pos | |||
self.a4 = 0 | |||
self.alpha4 = 1.5708 | |||
self.d4 = 0 | |||
self.theta4 = 1.5708 | |||
def print_dh_table(self): | |||
# Fancy print of the DH table here | |||
return 0 | |||
def get_hm_matrix(self, a, alpha, d, theta): | |||
# Angles are in radians | |||
Ax = np.array([(math.cos(theta), -math.sin(theta)*math.cos(alpha), math.sin(theta)*math.sin(alpha), a*math.cos(theta)), | |||
(math.sin(theta), math.cos(theta)*math.cos(alpha), -math.cos(theta)*math.sin(alpha), a*sin(theta)), | |||
(0, math.sin(alpha), math.cos(alpha), d), | |||
(0, 0, 0, 1)]) | |||
return Ax | |||
def hm_matrices(self): | |||
# Create numpy array to deal with matrix operations | |||
A1 = get_hm_matrix(self.a1, self.alpha1, self.d1, self.theta1) | |||
A2 = get_hm_matrix(self.a2, self.alpha2, self.d2, self.theta2) | |||
A3 = get_hm_matrix(self.a3, self.alpha3, self.d3, self.theta3) | |||
A4 = get_hm_matrix(self.a4, self.alpha4, self.d4, self.theta4) | |||
def compute_fk(self): | |||
An = A1*A2*A3*A4 | |||
return 0 | |||
def main(self): | |||
self.print_dh_table() | |||
return 0 | |||
if __name__ == '__main__': | |||
fk = ForwardKinematics(lift_pos=0, arm_pos=0, wrist_pos=0) | |||
fk.main() |
@ -0,0 +1,78 @@ | |||
#!/usr/bin/env python3 | |||
# This node will publish a spherical marker above the current robot position, as | |||
# it drives around the world | |||
import rclpy | |||
from rclpy.node import Node | |||
# Import the Marker message type from the visualization_msgs package | |||
from visualization_msgs.msg import Marker | |||
class Balloon(Node): | |||
def __init__(self): | |||
# Initialize the node named marker | |||
super().__init__('stretch_marker') | |||
# Set up a publisher that will publish on a topic called balloon | |||
self.publisher_ = self.create_publisher(Marker, 'balloon', 10) | |||
# Markers of all shapes share a common type | |||
self.marker = Marker() | |||
# The frame ID is the frame in which the position of the marker is specified | |||
# Refer to the wiki page for more marker types | |||
self.marker.header.frame_id = '/base_link' | |||
self.marker.header.stamp = self.get_clock().now().to_msg() | |||
self.marker.type = self.marker.SPHERE | |||
# If you have more than one marker that you want displayed at a given time, | |||
# then each needs to have a unique ID number | |||
self.marker.id = 0 | |||
# You can Add, Delete, or Modify markers by defining the action | |||
self.marker.action = self.marker.ADD | |||
# Define the size parameters for the marker | |||
self.marker.scale.x = 0.5 | |||
self.marker.scale.y = 0.5 | |||
self.marker.scale.z = 0.5 | |||
# Define the color as an RGB triple from 0 to 1 | |||
self.marker.color.r = 1.0 | |||
self.marker.color.g = 0.0 | |||
self.marker.color.b = 0.0 | |||
# Define the Alpha value, from 0 (invisible) to 1 (opaque) | |||
self.marker.color.a = 1.0 | |||
# Specify the pose of the marker. Since spheres are rotationally invariant, | |||
# we're only going to specify the positional elements. These are | |||
# in the coordinate frame named in frame_id. In this case, the position | |||
# will always be directly above the robot, and will move with it | |||
self.marker.pose.position.x = 0.0 | |||
self.marker.pose.position.y = 0.0 | |||
self.marker.pose.position.z = 2.0 | |||
self.get_logger().info("Publishing the balloon topic. Use RViz to visualize.") | |||
def publish_marker(self): | |||
# publisher the marker | |||
self.publisher_.publish(self.marker) | |||
def main(args=None): | |||
rclpy.init(args=args) | |||
balloon = Balloon() | |||
# This will loop until ROS shuts down the node. This can be done on the | |||
# command line with a ctrl-C, or automatically from roslaunch. | |||
while rclpy.ok(): | |||
balloon.publish_marker() | |||
balloon.destroy_node() | |||
rclpy.shutdown() | |||
if __name__ == '__main__': | |||
main() |
@ -0,0 +1,57 @@ | |||
#!/usr/bin/env python3 | |||
import rclpy | |||
from rclpy.node import Node | |||
# Imports the built-in Twist message type that the node uses to structure the velocity data | |||
from geometry_msgs.msg import Twist | |||
class Move(Node): | |||
def __init__(self): | |||
super().__init__('stretch_base_move') | |||
# Setup a publisher that will send the velocity commands to the base | |||
# This will publish on a topic called "/stretch/cmd_vel" with a message type Twist | |||
self.publisher_ = self.create_publisher(Twist, '/stretch/cmd_vel', 10) | |||
self.get_logger().info("Starting to move in circle...") | |||
timer_period = 0.5 # seconds | |||
self.timer = self.create_timer(timer_period, self.move_around) | |||
def move_around(self): | |||
# Define a Twist message | |||
command = Twist() | |||
# A Twist has three linear velocities (in meters per second), along each of the axes | |||
# Stretch has a ddifferential drive base and can't move laterally, it will only pay | |||
# attention to velocity along the x-axis | |||
command.linear.x = 0.0 | |||
command.linear.y = 0.0 | |||
command.linear.z = 0.0 | |||
# A Twist also has three rotational velocities (in radians per second) | |||
# Stretch will only respond to rotations along the z-axis | |||
command.angular.x = 0.0 | |||
command.angular.y = 0.0 | |||
command.angular.z = 0.5 | |||
# Publish the Twist message | |||
self.publisher_.publish(command) | |||
def main(args=None): | |||
rclpy.init(args=args) | |||
base_motion = Move() | |||
rclpy.spin(base_motion) | |||
base_motion.destroy_node() | |||
rclpy.shutdown() | |||
if __name__ == '__main__': | |||
main() | |||
@ -0,0 +1,133 @@ | |||
#!/usr/bin/env python3 | |||
# Every python controller needs these lines | |||
import sys | |||
import rclpy | |||
from rclpy.node import Node | |||
from rclpy.action import ActionClient | |||
from rclpy.duration import Duration | |||
# Import the FollowJointTrajectoryGoal from the control_msgs.action package to | |||
# control the Stretch robot. | |||
from control_msgs.action import FollowJointTrajectory | |||
# Import JointTrajectoryPoint from the trajectory_msgs package to define | |||
# robot trajectories. | |||
from trajectory_msgs.msg import JointTrajectoryPoint | |||
from sensor_msgs.msg import JointState | |||
class MultiPointCommand(Node): | |||
# Initialize the inhereted hm.Hellonode class. | |||
def __init__(self): | |||
super().__init__('stretch_multipoint_command') | |||
# Create an action client that can communicate with the /stretch_controller/follow_joint_trajectory action server | |||
self.trajectory_client = ActionClient(self, FollowJointTrajectory, '/stretch_controller/follow_joint_trajectory') | |||
server_reached = self.trajectory_client.wait_for_server(timeout_sec=60.0) | |||
if not server_reached: | |||
self.get_logger().error('Unable to connect to arm action server. Timeout exceeded.') | |||
sys.exit() | |||
# Create a subscriber for the /stretch/joint_states topic | |||
self.subscription = self.create_subscription(JointState, '/stretch/joint_states', self.joint_states_callback, 1) | |||
self.subscription | |||
self.get_logger().info('issuing multipoint command...') | |||
# Callback function for the /stretch/joint_states topic | |||
def joint_states_callback(self, joint_state): | |||
self.joint_state = joint_state | |||
def issue_multipoint_command(self): | |||
joint_state = self.joint_state | |||
duration0 = Duration(seconds=0.0) | |||
duration1 = Duration(seconds=2.0) | |||
duration2 = Duration(seconds=4.0) | |||
duration3 = Duration(seconds=6.0) | |||
duration4 = Duration(seconds=8.0) | |||
duration5 = Duration(seconds=10.0) | |||
# Provide desired positions of lift, wrist extension, and yaw of | |||
# the wrist | |||
joint_value1 = joint_state.position[1] # joint_lift is at index 1 | |||
joint_value2 = joint_state.position[0] # wrist_extension is at index 0 | |||
joint_value3 = joint_state.position[8] # joint_wrist_yaw is at index 8 | |||
# Set point0 as a JointTrajectoryPoint(). | |||
point0 = JointTrajectoryPoint() | |||
# Provide desired positions of lift, wrist extension, and yaw of | |||
# the wrist (in meters). | |||
point0.positions = [joint_value1, joint_value2, joint_value3] | |||
# Provide desired velocity of the lift (m/s), wrist extension (m/s), | |||
# and wrist yaw (rad/s). | |||
# IMPORTANT NOTE: The lift and wrist extension can only go up to 0.2 m/s! | |||
point0.velocities = [0.2, 0.2, 2.5] | |||
# Provide desired velocity of the lift (m/s^2), wrist extension (m/s^2), | |||
# and wrist yaw (rad/s^2). | |||
point0.accelerations = [1.0, 1.0, 3.5] | |||
point0.time_from_start = duration0.to_msg() | |||
# Set positions for the following 5 trajectory points. | |||
# IMPORTANT NOTE: If you do not provide any velocities or accelerations for the lift | |||
# or wrist extension, then they go to their default values. However, the | |||
# Velocity and Acceleration of the wrist yaw will stay the same from the | |||
# previous value unless updated. | |||
point1 = JointTrajectoryPoint() | |||
point1.positions = [0.3, 0.1, 2.0] | |||
point1.time_from_start = duration1.to_msg() | |||
point2 = JointTrajectoryPoint() | |||
point2.positions = [0.5, 0.2, -1.0] | |||
point2.time_from_start = duration2.to_msg() | |||
point3 = JointTrajectoryPoint() | |||
point3.positions = [0.6, 0.3, 0.0] | |||
point3.time_from_start = duration3.to_msg() | |||
point4 = JointTrajectoryPoint() | |||
point4.positions = [0.8, 0.2, 1.0] | |||
point4.time_from_start = duration4.to_msg() | |||
point5 = JointTrajectoryPoint() | |||
point5.positions = [0.5, 0.1, 0.0] | |||
point5.time_from_start = duration5.to_msg() | |||
# Set trajectory_goal as a FollowJointTrajectoryGoal and define | |||
# the joint names as a list. | |||
trajectory_goal = FollowJointTrajectory.Goal() | |||
trajectory_goal.trajectory.joint_names = ['joint_lift', 'wrist_extension', 'joint_wrist_yaw'] | |||
# Then trajectory_goal.trajectory.points is defined by a list of the joint | |||
# trajectory points | |||
trajectory_goal.trajectory.points = [point0, point1, point2, point3, point4, point5] | |||
# Specify the coordinate frame that we want (base_link) and set the time to be now. | |||
trajectory_goal.trajectory.header.stamp = self.get_clock().now().to_msg() | |||
trajectory_goal.trajectory.header.frame_id = 'base_link' | |||
# Make the action call and send the goal. The last line of code waits | |||
# for the result before it exits the python script. | |||
self.trajectory_client.send_goal_async(trajectory_goal) | |||
self.get_logger().info('Sent stow goal = {0}'.format(trajectory_goal)) | |||
def main(args=None): | |||
rclpy.init(args=args) | |||
multipoint_command = MultiPointCommand() | |||
rclpy.spin_once(multipoint_command) | |||
multipoint_command.issue_multipoint_command() | |||
rclpy.spin(multipoint_command) | |||
multipoint_command.destroy_node() | |||
rclpy.shutdown() | |||
if __name__ == '__main__': | |||
main() |
@ -1,52 +1,68 @@ | |||
#!/usr/bin/env python | |||
#!/usr/bin/env python3 | |||
# Every python controller needs these lines | |||
import rospy | |||
import rclpy | |||
from rclpy.node import Node | |||
from numpy import linspace, inf | |||
from math import sin | |||
# We're going to subscribe to a LaserScan message. | |||
# We're going to subscribe to a LaserScan message | |||
from sensor_msgs.msg import LaserScan | |||
class Scanfilter: | |||
class ScanFilter(Node): | |||
def __init__(self): | |||
# Initialize a ROS node named scan_filter | |||
super().__init__('stretch_scan_filter') | |||
# Set up a publisher that will publish on a topic called "filtered_scan", | |||
# with a LaserScan message type | |||
self.pub = self.create_publisher(LaserScan, '/filtered_scan', 10) #/stretch_diff_drive_controller/cmd_vel for gazebo | |||
# Set up a subscriber. We're going to subscribe to the topic "scan", | |||
# looking for LaserScan messages. When a message comes in, ROS is going | |||
# to pass it to the function "callback" automatically | |||
self.sub = self.create_subscription(LaserScan, '/scan', self.scan_filter_callback, 10) | |||
# We're going to assume that the robot is pointing up the x-axis, so that | |||
# any points with y coordinates further than half of the defined | |||
# width (1 meter) from the axis are not considered. | |||
# width (1 meter) from the axis are not considered | |||
self.width = 1 | |||
self.extent = self.width / 2.0 | |||
# Set up a subscriber. We're going to subscribe to the topic "scan", | |||
# looking for LaserScan messages. When a message comes in, ROS is going | |||
# to pass it to the function "callback" automatically. | |||
self.sub = rospy.Subscriber('/scan', LaserScan, self.callback) | |||
self.get_logger().info("Publishing the filtered_scan topic. Use RViz to visualize.") | |||
# Set up a publisher. This will publish on a topic called "filtered_scan", | |||
# with a LaserScan message type. | |||
self.pub = rospy.Publisher('filtered_scan', LaserScan, queue_size=10) | |||
def callback(self,msg): | |||
def scan_filter_callback(self,msg): | |||
# Figure out the angles of the scan. We're going to do this each time, in case we're subscribing to more than one | |||
# laser, with different numbers of beams. | |||
# laser, with different numbers of beams | |||
angles = linspace(msg.angle_min, msg.angle_max, len(msg.ranges)) | |||
# Work out the y coordinates of the ranges. | |||
# Work out the y coordinates of the ranges | |||
points = [r * sin(theta) if (theta < -2.5 or theta > 2.5) else inf for r,theta in zip(msg.ranges, angles)] | |||
# If we're close to the x axis, keep the range, otherwise use inf, which means "no return". | |||
# If we're close to the x axis, keep the range, otherwise use inf, which means "no return" | |||
new_ranges = [r if abs(y) < self.extent else inf for r,y in zip(msg.ranges, points)] | |||
# Substitute in the new ranges in the original message, and republish it. | |||
# Substitute in the new ranges in the original message, and republish it | |||
msg.ranges = new_ranges | |||
self.pub.publish(msg) | |||
if __name__ == '__main__': | |||
# Initialize the node, and call it "scan_filter". | |||
rospy.init_node('scan_filter') | |||
# Setup Scanfilter class | |||
Scanfilter() | |||
def main(args=None): | |||
# First the rclpy library is initialized | |||
rclpy.init(args=args) | |||
# Create object of the ScanFilter class | |||
scan_filter = ScanFilter() | |||
# Give control to ROS. This will allow the callback to be called whenever new | |||
# messages come in. If we don't put this line in, then the node will not work, | |||
# and ROS will not process any messages. | |||
rospy.spin() | |||
# and ROS will not process any messages | |||
rclpy.spin(scan_filter) | |||
scan_filter.destroy_node() | |||
rclpy.shutdown() | |||
if __name__ == '__main__': | |||
main() |
@ -0,0 +1,105 @@ | |||
#!/usr/bin/env python3 | |||
# Every python controller needs these lines | |||
import rclpy | |||
from rclpy.node import Node | |||
from rclpy.duration import Duration | |||
from rclpy.action import ActionClient | |||
import sys | |||
# Import the FollowJointTrajectoryGoal from the control_msgs.action package to | |||
# control the Stretch robot. | |||
from control_msgs.action import FollowJointTrajectory | |||
# Import JointTrajectoryPoint from the trajectory_msgs package to define | |||
# robot trajectories and JointState from sensor_msgs to store joint states | |||
from trajectory_msgs.msg import JointTrajectoryPoint | |||
from sensor_msgs.msg import JointState | |||
class StowCommand(Node): | |||
def __init__(self): | |||
# Initialize a ROS node named stow_command | |||
super().__init__('stretch_stow_command') | |||
self.joint_state = JointState() | |||
# Create an action client that can communicate with the /stretch_controller/follow_joint_trajectory action server | |||
self.trajectory_client = ActionClient(self, FollowJointTrajectory, '/stretch_controller/follow_joint_trajectory') | |||
server_reached = self.trajectory_client.wait_for_server(timeout_sec=60.0) | |||
if not server_reached: | |||
self.get_logger().error('Unable to connect to arm action server. Timeout exceeded.') | |||
sys.exit() | |||
# Create a subscriber for the /stretch/joint_states topic | |||
self.subscription = self.create_subscription(JointState, '/stretch/joint_states', self.joint_states_callback, 1) | |||
self.subscription | |||
# Callback function for the /stretch/joint_states topic | |||
def joint_states_callback(self, joint_state): | |||
self.joint_state = joint_state | |||
def issue_stow_command(self): | |||
joint_state = self.joint_state | |||
if (joint_state is not None): | |||
self.get_logger().info('stowing...') | |||
# Set two points as JointTrajectoryPoint(): stow_point1 is the current state, | |||
# stow_point2 is the goal state | |||
stow_point1 = JointTrajectoryPoint() | |||
stow_point2 = JointTrajectoryPoint() | |||
duration1 = Duration(seconds=0.0) | |||
duration2 = Duration(seconds=4.0) | |||
stow_point1.time_from_start = duration1.to_msg() | |||
stow_point2.time_from_start = duration2.to_msg() | |||
# Provide desired positions of lift, wrist extension, and yaw of | |||
# the wrist | |||
joint_value1 = joint_state.position[1] # joint_lift is at index 1 | |||
joint_value2 = joint_state.position[0] # wrist_extension is at index 0 | |||
joint_value3 = joint_state.position[8] # joint_wrist_yaw is at index 8 | |||
stow_point1.positions = [joint_value1, joint_value2, joint_value3] | |||
stow_point2.positions = [0.2, 0.0, 3.14] | |||
# Set trajectory_goal as a FollowJointTrajectoryGoal and define | |||
# the joint names as a list. | |||
trajectory_goal = FollowJointTrajectory.Goal() | |||
trajectory_goal.trajectory.joint_names = ['joint_lift', 'wrist_extension', 'joint_wrist_yaw'] | |||
# Then trajectory_goal.trajectory.points is defined by the positions | |||
# set in stow_point1 and stow_point2 | |||
trajectory_goal.trajectory.points = [stow_point1, stow_point2] | |||
# Specify the coordinate frame that we want (base_link) and set the time to be now | |||
trajectory_goal.trajectory.header.stamp = self.get_clock().now().to_msg() | |||
trajectory_goal.trajectory.header.frame_id = 'base_link' | |||
# Make the action call and send the goal | |||
self.trajectory_client.send_goal_async(trajectory_goal) | |||
self.get_logger().info('Sent stow goal = {0}'.format(trajectory_goal)) | |||
# Create a funcion, main(), to do all of the setup the hm.HelloNode class | |||
# and issue the stow command. | |||
def main(args=None): | |||
rclpy.init(args=args) | |||
# Create object of the StowCommand class | |||
stow_command = StowCommand() | |||
# spin_once allows the joint_states_callback functions to be executed once | |||
# for joint states to be received to this node | |||
rclpy.spin_once(stow_command) | |||
stow_command.issue_stow_command() | |||
rclpy.spin(stow_command) | |||
# Destroy the node explicitly | |||
# (optional - otherwise it will be done automatically | |||
# when the garbage collector destroys the node object) | |||
stow_command.destroy_node() | |||
rclpy.shutdown() | |||
if __name__ == '__main__': | |||
main() |
@ -0,0 +1,88 @@ | |||
from geometry_msgs.msg import TransformStamped | |||
import rclpy | |||
from rclpy.node import Node | |||
from tf2_ros import TransformBroadcaster | |||
import tf_transformations | |||
# This node publishes three child static frames in reference to their parent frames as below: | |||
# parent -> link_mast child -> fk_link_mast | |||
# parent -> link_lift child -> fk_link_lift | |||
# parent -> link_wrist_yaw child -> fk_link_wrist_yaw | |||
# Tf frames are not defined according to the DH convention by default | |||
# The new frames have been declared to make the computation of | |||
# forward and inverse kinematics easier by defining the new frames | |||
# according to the DH convention | |||
class FixedFrameBroadcaster(Node): | |||
def __init__(self): | |||
super().__init__('stretch_tf_broadcaster') | |||
self.br = TransformBroadcaster(self) | |||
time_period = 0.1 # seconds | |||
self.timer = self.create_timer(time_period, self.broadcast_timer_callback) | |||
self.mast = TransformStamped() | |||
self.mast.header.frame_id = 'link_mast' | |||
self.mast.child_frame_id = 'fk_link_mast' | |||
self.mast.transform.translation.x = 0.0 | |||
self.mast.transform.translation.y = 0.0 | |||
self.mast.transform.translation.z = 0.0 | |||
q = tf_transformations.quaternion_from_euler(1.5707, 0, -1.5707) | |||
self.mast.transform.rotation.x = q[0] | |||
self.mast.transform.rotation.y = q[1] | |||
self.mast.transform.rotation.z = q[2] | |||
self.mast.transform.rotation.w = q[3] | |||
self.lift = TransformStamped() | |||
self.lift.header.stamp = self.get_clock().now().to_msg() | |||
self.lift.header.frame_id = 'link_lift' | |||
self.lift.child_frame_id = 'fk_link_lift' | |||
self.lift.transform.translation.x = 0.0 | |||
self.lift.transform.translation.y = 2.0 | |||
self.lift.transform.translation.z = 0.0 | |||
q = tf_transformations.quaternion_from_euler(1.5707, 0, -1.5707) | |||
self.lift.transform.rotation.x = q[0] | |||
self.lift.transform.rotation.y = q[1] | |||
self.lift.transform.rotation.z = q[2] | |||
self.lift.transform.rotation.w = q[3] | |||
self.br.sendTransform(self.lift) | |||
self.wrist = TransformStamped() | |||
self.wrist.header.stamp = self.get_clock().now().to_msg() | |||
self.wrist.header.frame_id = 'link_wrist_yaw' | |||
self.wrist.child_frame_id = 'fk_link_wrist_yaw' | |||
self.wrist.transform.translation.x = 0.0 | |||
self.wrist.transform.translation.y = 2.0 | |||
self.wrist.transform.translation.z = 0.0 | |||
q = tf_transformations.quaternion_from_euler(1.5707, 0, -1.5707) | |||
self.wrist.transform.rotation.x = q[0] | |||
self.wrist.transform.rotation.y = q[1] | |||
self.wrist.transform.rotation.z = q[2] | |||
self.wrist.transform.rotation.w = q[3] | |||
self.br.sendTransform(self.wrist) | |||
self.get_logger().info("Publishing Tf frames. Use RViz to visualize.") | |||
def broadcast_timer_callback(self): | |||
self.mast.header.stamp = self.get_clock().now().to_msg() | |||
self.br.sendTransform(self.mast) | |||
self.lift.header.stamp = self.get_clock().now().to_msg() | |||
self.br.sendTransform(self.lift) | |||
self.wrist.header.stamp = self.get_clock().now().to_msg() | |||
self.br.sendTransform(self.wrist) | |||
def main(args=None): | |||
rclpy.init(args=args) | |||
tf_broadcaster = FixedFrameBroadcaster() | |||
rclpy.spin(tf_broadcaster) | |||
tf_broadcaster.destroy_node() | |||
rclpy.shutdown() | |||
if __name__ == '__main__': | |||
main() |
@ -0,0 +1,63 @@ | |||
import rclpy | |||
from rclpy.node import Node | |||
from rclpy.time import Time | |||
from tf2_ros import TransformException | |||
from tf2_ros.buffer import Buffer | |||
from tf2_ros.transform_listener import TransformListener | |||
# This node prints the transformation between the fk_link_mast frame and a given target frame | |||
# The default target frame is the link_grasp_center frame | |||
# To change the target frame just pass it as an argument to the node, for ex. "target_frame:='fk_link_lift'" | |||
class FrameListener(Node): | |||
def __init__(self): | |||
super().__init__('stretch_tf_listener') | |||
# Declare and acquire `target_frame` parameter | |||
self.declare_parameter('target_frame', 'link_grasp_center') | |||
self.target_frame = self.get_parameter( | |||
'target_frame').get_parameter_value().string_value | |||
# Start a Tf buffer that will store the tf information for a few seconds | |||
self.tf_buffer = Buffer() | |||
self.tf_listener = TransformListener(self.tf_buffer, self) | |||
# Call on_timer function every second | |||
time_period = 1.0 # seconds | |||
self.timer = self.create_timer(time_period, self.on_timer) | |||
def on_timer(self): | |||
# Store frame names in variables that will be used to | |||
# compute transformations | |||
from_frame_rel = self.target_frame | |||
to_frame_rel = 'fk_link_mast' | |||
# Look up the transformation between target_frame and link_mast frames | |||
try: | |||
now = Time() | |||
trans = self.tf_buffer.lookup_transform( | |||
to_frame_rel, | |||
from_frame_rel, | |||
now) | |||
except TransformException as ex: | |||
self.get_logger().info( | |||
f'Could not transform {to_frame_rel} to {from_frame_rel}: {ex}') | |||
return | |||
self.get_logger().info( | |||
f'the pose of target frame {from_frame_rel} with reference to {to_frame_rel} is: {trans}') | |||
def main(): | |||
rclpy.init() | |||
node = FrameListener() | |||
try: | |||
rclpy.spin(node) | |||
except KeyboardInterrupt: | |||
pass | |||
rclpy.shutdown() | |||
if __name__ == '__main__': | |||
main() |
@ -0,0 +1,23 @@ | |||
# Copyright 2015 Open Source Robotics Foundation, Inc. | |||
# | |||
# Licensed under the Apache License, Version 2.0 (the "License"); | |||
# you may not use this file except in compliance with the License. | |||
# You may obtain a copy of the License at | |||
# | |||
# http://www.apache.org/licenses/LICENSE-2.0 | |||
# | |||
# Unless required by applicable law or agreed to in writing, software | |||
# distributed under the License is distributed on an "AS IS" BASIS, | |||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
# See the License for the specific language governing permissions and | |||
# limitations under the License. | |||
from ament_copyright.main import main | |||
import pytest | |||
@pytest.mark.copyright | |||
@pytest.mark.linter | |||
def test_copyright(): | |||
rc = main(argv=['.', 'test']) | |||
assert rc == 0, 'Found errors' |
@ -0,0 +1,25 @@ | |||
# Copyright 2017 Open Source Robotics Foundation, Inc. | |||
# | |||
# Licensed under the Apache License, Version 2.0 (the "License"); | |||
# you may not use this file except in compliance with the License. | |||
# You may obtain a copy of the License at | |||
# | |||
# http://www.apache.org/licenses/LICENSE-2.0 | |||
# | |||
# Unless required by applicable law or agreed to in writing, software | |||
# distributed under the License is distributed on an "AS IS" BASIS, | |||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
# See the License for the specific language governing permissions and | |||
# limitations under the License. | |||
from ament_flake8.main import main_with_errors | |||
import pytest | |||
@pytest.mark.flake8 | |||
@pytest.mark.linter | |||
def test_flake8(): | |||
rc, errors = main_with_errors(argv=[]) | |||
assert rc == 0, \ | |||
'Found %d code style errors / warnings:\n' % len(errors) + \ | |||
'\n'.join(errors) |
@ -0,0 +1,23 @@ | |||
# Copyright 2015 Open Source Robotics Foundation, Inc. | |||
# | |||
# Licensed under the Apache License, Version 2.0 (the "License"); | |||
# you may not use this file except in compliance with the License. | |||
# You may obtain a copy of the License at | |||
# | |||
# http://www.apache.org/licenses/LICENSE-2.0 | |||
# | |||
# Unless required by applicable law or agreed to in writing, software | |||
# distributed under the License is distributed on an "AS IS" BASIS, | |||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
# See the License for the specific language governing permissions and | |||
# limitations under the License. | |||
from ament_pep257.main import main | |||
import pytest | |||
@pytest.mark.linter | |||
@pytest.mark.pep257 | |||
def test_pep257(): | |||
rc = main(argv=['.', 'test']) | |||
assert rc == 0, 'Found code style errors / warnings' |