mirror of
https://github.com/wassname/pyrobolearn.git
synced 2026-09-21 13:20:37 +08:00
263 lines
11 KiB
Python
263 lines
11 KiB
Python
# -*- coding: utf-8 -*-
|
|
#!/usr/bin/env python
|
|
"""Define the ROS API.
|
|
|
|
Dependencies in PRL:
|
|
* `pyrobolearn.simulators.simulator.Simulator`
|
|
"""
|
|
|
|
# TODO
|
|
import os
|
|
import subprocess
|
|
import psutil
|
|
import signal
|
|
import importlib
|
|
import inspect
|
|
|
|
from pyrobolearn.simulators.simulator import Simulator
|
|
|
|
|
|
__author__ = "Brian Delhaisse"
|
|
__copyright__ = "Copyright 2018, PyRoboLearn"
|
|
__credits__ = ["ROS (Willow Garage)", "Brian Delhaisse"]
|
|
__license__ = "GNU GPLv3"
|
|
__version__ = "1.0.0"
|
|
__maintainer__ = "Brian Delhaisse"
|
|
__email__ = "briandelhaisse@gmail.com"
|
|
__status__ = "Development"
|
|
|
|
|
|
# TODO: maybe I should inherit from MiddleWare instead of Simulator... Then we can give these MiddleWare to different
|
|
# simulators. Other communication middleware layer includes YARP, etc.
|
|
|
|
class ROS(Simulator):
|
|
r"""ROS Interface
|
|
"""
|
|
|
|
def __init__(self, subscribe=False, publish=False, teleoperate=False, master_uri=11311, **kwargs):
|
|
super(ROS, self).__init__(render=False)
|
|
|
|
# Environment variable
|
|
self.env = os.environ.copy()
|
|
self.env["ROS_MASTER_URI"] = "http://localhost:" + str(master_uri)
|
|
|
|
# this is for the rospy methods such as: wait_for_service(), init_node(), ...
|
|
os.environ['ROS_MASTER_URI'] = self.env['ROS_MASTER_URI']
|
|
|
|
# run ROS core if not already running
|
|
self.roscore = None
|
|
if "roscore" not in [p.name() for p in psutil.process_iter()]:
|
|
# subprocess.Popen("roscore", env=self.env)
|
|
self.roscore = subprocess.Popen(["roscore", "-p", str(master_uri)], env=self.env,
|
|
preexec_fn=os.setsid) # , shell=True)
|
|
|
|
# set variables
|
|
self.subscribe = subscribe
|
|
self.publish = publish
|
|
self.teleoperate = teleoperate
|
|
|
|
# remember each publisher/subscriber
|
|
self.subscribers = {}
|
|
self.publishers = {}
|
|
self.models = []
|
|
|
|
self.count_id = -1
|
|
|
|
def close(self):
|
|
"""
|
|
Close everything
|
|
"""
|
|
# delete each subscribers
|
|
|
|
# delete each publishers
|
|
|
|
# delete ROS
|
|
if self.roscore is not None:
|
|
os.killpg(os.getpgid(self.roscore.pid), signal.SIGTERM)
|
|
|
|
@property
|
|
def is_subscribing(self):
|
|
"""Return True if we are subscribing to topics."""
|
|
return self.subscribe
|
|
|
|
@property
|
|
def is_publishing(self):
|
|
"""Return True if we are publishing to topics."""
|
|
return self.publish
|
|
|
|
def load_urdf(self, filename, position=None, orientation=None, use_maximal_coordinates=None,
|
|
use_fixed_base=None, flags=None, scale=None):
|
|
"""Load the given URDF file.
|
|
|
|
The load_urdf will send a command to the physics server to load a physics model from a Universal Robot
|
|
Description File (URDF). The URDF file is used by the ROS project (Robot Operating System) to describe robots
|
|
and other objects, it was created by the WillowGarage and the Open Source Robotics Foundation (OSRF).
|
|
Many robots have public URDF files, you can find a description and tutorial here:
|
|
http://wiki.ros.org/urdf/Tutorials
|
|
|
|
Args:
|
|
filename (str): a relative or absolute path to the URDF file on the file system of the physics server.
|
|
position (vec3): create the base of the object at the specified position in world space coordinates [x,y,z]
|
|
orientation (quat): create the base of the object at the specified orientation as world space quaternion
|
|
[x,y,z,w]
|
|
use_maximal_coordinates (int): Experimental. By default, the joints in the URDF file are created using the
|
|
reduced coordinate method: the joints are simulated using the Featherstone Articulated Body algorithm
|
|
(btMultiBody in Bullet 2.x). The useMaximalCoordinates option will create a 6 degree of freedom rigid
|
|
body for each link, and constraints between those rigid bodies are used to model joints.
|
|
use_fixed_base (bool): force the base of the loaded object to be static
|
|
flags (int): URDF_USE_INERTIA_FROM_FILE (val=2): by default, Bullet recomputed the inertia tensor based on
|
|
mass and volume of the collision shape. If you can provide more accurate inertia tensor, use this flag.
|
|
URDF_USE_SELF_COLLISION (val=8): by default, Bullet disables self-collision. This flag let's you
|
|
enable it.
|
|
You can customize the self-collision behavior using the following flags:
|
|
* URDF_USE_SELF_COLLISION_EXCLUDE_PARENT (val=16) will discard self-collision between links that
|
|
are directly connected (parent and child).
|
|
* URDF_USE_SELF_COLLISION_EXCLUDE_ALL_PARENTS (val=32) will discard self-collisions between a
|
|
child link and any of its ancestors (parents, parents of parents, up to the base).
|
|
* URDF_USE_IMPLICIT_CYLINDER (val=128), will use a smooth implicit cylinder. By default, Bullet
|
|
will tessellate the cylinder into a convex hull.
|
|
scale (float): scale factor to the URDF model.
|
|
|
|
Returns:
|
|
int (non-negative): unique id associated to the load model.
|
|
"""
|
|
id_ = self.count_id
|
|
self.count_id += 1
|
|
|
|
# get path to directory of urdf
|
|
path = os.path.dirname(os.path.abspath(filename)) # /path/to/pyrobolearn/robots/urdfs/<robot>/
|
|
robot_directory_name = path.split('/')[-1] # <robot>
|
|
path = path + '/../../ros/' + robot_directory_name + '/' # /path/to/pyrobolearn/robots/ros/<robot>/
|
|
|
|
# check if valid robot directory
|
|
# if os.path.isdir(path):
|
|
robot_path = '/'.join(path.split('/')[-5:-2])
|
|
if robot_path == 'pyrobolearn/robots/urdfs':
|
|
|
|
# get corresponding subscriber/publisher
|
|
def check_ros(name, dictionary, id_):
|
|
# TODO: do I really need a class for each robot? Can I not just use RobotPublisher?
|
|
# if os.path.isfile(path + name + '.py'):
|
|
# module = importlib.import_module('pyrobolearn.robots.ros.' + robot_directory_name + '.' + name)
|
|
# classes = inspect.getmembers(module, inspect.isclass) # list of (name, class)
|
|
# length = len(name)
|
|
# robot_name = ''.join(robot_directory_name.split('_'))
|
|
#
|
|
# # go through each class and get the :attr:`name` corresponding to the robot and add it to the
|
|
# # given :attr:`dictionary`
|
|
# for name, cls in classes:
|
|
# if name[:-length].lower() == robot_name:
|
|
# dictionary[id_] = cls(id_=id_)
|
|
# break
|
|
module = importlib.import_module('pyrobolearn.robots.ros.' + name)
|
|
classes = dict(inspect.getmembers(module, inspect.isclass))
|
|
cls = classes['Robot' + name.capitalize()]
|
|
dictionary[id_] = cls(name=robot_directory_name, id_=id_)
|
|
|
|
# load subscriber in simulator
|
|
if self.subscribe:
|
|
check_ros('subscriber', self.subscribers, id_)
|
|
|
|
# load publisher in simulator
|
|
if self.publish:
|
|
check_ros('publisher', self.publishers, id_)
|
|
|
|
return id_
|
|
|
|
def get_joint_positions(self, body_id, joint_ids):
|
|
"""
|
|
Get the position of the given joint(s).
|
|
|
|
Args:
|
|
body_id (int): unique body id.
|
|
joint_ids (int, list of int): joint id, or list of joint ids.
|
|
|
|
Returns:
|
|
if 1 joint:
|
|
float: joint position [rad]
|
|
if multiple joints:
|
|
np.float[N]: joint positions [rad]
|
|
"""
|
|
if body_id in self.subscribers:
|
|
return self.subscribers[body_id].get_joint_positions(joint_ids)
|
|
|
|
def set_joint_positions(self, body_id, joint_ids, positions, velocities=None, kps=None, kds=None, forces=None):
|
|
"""
|
|
Set the position of the given joint(s) (using position control).
|
|
|
|
Args:
|
|
body_id (int): unique body id.
|
|
joint_ids (int, list of int): joint id, or list of joint ids.
|
|
positions (float, np.float[N]): desired position, or list of desired positions [rad]
|
|
velocities (None, float, np.float[N]): desired velocity, or list of desired velocities [rad/s]
|
|
kps (None, float, np.float[N]): position gain(s)
|
|
kds (None, float, np.float[N]): velocity gain(s)
|
|
forces (None, float, np.float[N]): maximum motor force(s)/torque(s) used to reach the target values.
|
|
"""
|
|
if body_id in self.publishers:
|
|
self.publishers[body_id].set_joint_positions(joint_ids, positions)
|
|
self.publishers[body_id].publish('joint_states')
|
|
|
|
def get_joint_velocities(self, body_id, joint_ids):
|
|
"""
|
|
Get the velocity of the given joint(s).
|
|
|
|
Args:
|
|
body_id (int): unique body id.
|
|
joint_ids (int, list of int): joint id, or list of joint ids.
|
|
|
|
Returns:
|
|
if 1 joint:
|
|
float: joint velocity [rad/s]
|
|
if multiple joints:
|
|
np.float[N]: joint velocities [rad/s]
|
|
"""
|
|
if body_id in self.subscribers:
|
|
return self.subscribers[body_id].get_joint_velocities(joint_ids)
|
|
|
|
def set_joint_velocities(self, body_id, joint_ids, velocities, max_force=None):
|
|
"""
|
|
Set the velocity of the given joint(s) (using velocity control).
|
|
|
|
Args:
|
|
body_id (int): unique body id.
|
|
joint_ids (int, list of int): joint id, or list of joint ids.
|
|
velocities (float, np.float[N]): desired velocity, or list of desired velocities [rad/s]
|
|
max_force (None, float, np.float[N]): maximum motor forces/torques
|
|
"""
|
|
if body_id in self.publishers:
|
|
self.publishers[body_id].set_joint_velocities(joint_ids, velocities)
|
|
self.publishers[body_id].publish('joint_states')
|
|
|
|
def get_joint_torques(self, body_id, joint_ids):
|
|
"""
|
|
Get the applied torque(s) on the given joint(s). "This is the motor torque applied during the last `step`.
|
|
Note that this only applies in VELOCITY_CONTROL and POSITION_CONTROL. If you use TORQUE_CONTROL then the
|
|
applied joint motor torque is exactly what you provide, so there is no need to report it separately." [1]
|
|
|
|
Args:
|
|
body_id (int): unique body id.
|
|
joint_ids (int, list of int): a joint id, or list of joint ids.
|
|
|
|
Returns:
|
|
if 1 joint:
|
|
float: torque [Nm]
|
|
if multiple joints:
|
|
np.float[N]: torques associated to the given joints [Nm]
|
|
"""
|
|
if body_id in self.subscribers:
|
|
return self.subscribers[body_id].get_joint_torques(joint_ids)
|
|
|
|
def set_joint_torques(self, body_id, joint_ids, torques):
|
|
"""
|
|
Set the torque/force to the given joint(s) (using force/torque control).
|
|
|
|
Args:
|
|
body_id (int): unique body id.
|
|
joint_ids (int, list of int): joint id, or list of joint ids.
|
|
torques (float, list of float): desired torque(s) to apply to the joint(s) [N].
|
|
"""
|
|
if body_id in self.publishers:
|
|
self.publishers[body_id].set_joint_torques(joint_ids, torques)
|
|
self.publishers[body_id].publish('joint_states')
|