update simulators and ROS middleware integration

This commit is contained in:
Brian Delhaisse
2019-09-18 22:47:39 +02:00
parent 4e7fb22202
commit cd03a1f85f
18 changed files with 4068 additions and 200 deletions
+8 -8
View File
@@ -1629,10 +1629,10 @@ class Robot(ControllableBody):
Args:
link_ids (int, int[N], None): link id, or list of desired link ids. If None, get the state of all links
associated to actuated joints.
associated to actuated joints.
compute_link_velocity (bool): if True, the Cartesian world velocity will be computed and returned.
compute_forward_kinematics (bool): if True, the Cartesian world position/orientation will be recomputed
using forward kinematics.
using forward kinematics.
Returns:
if 1 link:
@@ -2696,9 +2696,9 @@ class Robot(ControllableBody):
Args:
link_id (int): link id.
q (np.array[float[N]], None): joint positions of size N, where N is the number of DoFs. If None, it will
compute q based on the current joint positions.
compute q based on the current joint positions.
local_position (None, np.array[float[3]]): the point on the specified link to compute the Jacobian (in link
local coordinates around its center of mass). If None, it will use the CoM position (in the link frame).
local coordinates around its center of mass). If None, it will use the CoM position (in the link frame).
Returns:
np.array[float[6,N]], np.array[float[6,6+N]]: full geometric (linear and angular) Jacobian matrix. The
@@ -2737,9 +2737,9 @@ class Robot(ControllableBody):
Args:
link_id (int): link id
q (np.array[float[N]]): joint positions of size N, where N is the number of DoFs. If None, it will compute
q based on the current joint positions.
q based on the current joint positions.
local_position: the point on the specified link to compute the Jacobian (in link local coordinates around
its center of mass). If None, it will use the CoM position (in the link frame).
its center of mass). If None, it will use the CoM position (in the link frame).
Returns:
np.array[float[3,N]], np.array[float[3,6+N]]: full linear geometric Jacobian matrix. The number of
@@ -2761,9 +2761,9 @@ class Robot(ControllableBody):
Args:
link_id (int): link id
q (np.array[float[N]]): joint positions of size N, where N is the number of DoFs. If None, it will compute
q based on the current joint positions.
q based on the current joint positions.
local_position: the point on the specified link to compute the Jacobian (in link local coordinates around
its center of mass). If None, it will use the CoM position (in the link frame).
its center of mass). If None, it will use the CoM position (in the link frame).
Returns:
np.array[float[3,N]], np.array[float[3,6+N]]: full angular geometric Jacobian matrix. The number of
+15
View File
@@ -0,0 +1,15 @@
rrbot:
# Publish all joint states -----------------------------------
joint_state_controller:
type: joint_state_controller/JointStateController
publish_rate: 50
# Position Controllers ---------------------------------------
joint1_position_controller:
type: effort_controllers/JointPositionController
joint: joint1
pid: {p: 100.0, i: 0.01, d: 10.0}
joint2_position_controller:
type: effort_controllers/JointPositionController
joint: joint2
pid: {p: 100.0, i: 0.01, d: 10.0}
+18 -5
View File
@@ -2,6 +2,9 @@
# load middlewares
from . import middlewares
# check Python version
import sys
python_version = sys.version_info[0]
# load all simulators
@@ -14,14 +17,24 @@ from .bullet import Bullet
# Bullet ros simulator
from .bullet_ros import BulletROS
# Dart simulator
# from .dart import Dart
if python_version >= 3:
# Dart simulator
try:
from .dart import Dart
except ImportError as e:
print("Dart not found.")
# MuJoCo simulator
# from .mujoco import Mujoco
# MuJoCo simulator
try:
from .mujoco import Mujoco
except ImportError as e:
print("MuJoCo not found.")
# Raisim simulator
# from .raisim import Raisim
try:
from .raisim import Raisim
except ImportError as e:
print("Raisim not found.")
# Vrep simulator (note that there is a currently a problem when loading pybullet with pyrep)
# from .vrep import VREP
+11 -11
View File
@@ -1735,15 +1735,15 @@ class Bullet(Simulator):
link_id (int): link index.
compute_velocity (bool): If True, the Cartesian world velocity will be computed and returned.
compute_forward_kinematics (bool): if True, the Cartesian world position/orientation will be recomputed
using forward kinematics.
using forward kinematics.
Returns:
np.array[float[3]]: Cartesian world position of CoM
np.array[float[4]]: Cartesian world orientation of CoM, in quaternion [x,y,z,w]
np.array[float[3]]: local position offset of inertial frame (center of mass) expressed in the URDF
link frame
link frame
np.array[float[4]]: local orientation (quaternion [x,y,z,w]) offset of the inertial frame expressed in URDF
link frame
link frame
np.array[float[3]]: world position of the URDF link frame
np.array[float[4]]: world orientation of the URDF link frame (expressed as a quaternion [x,y,z,w])
np.array[float[3]]: Cartesian world linear velocity. Only returned if `compute_velocity` is True.
@@ -2214,7 +2214,7 @@ class Bullet(Simulator):
return np.asarray(self.sim.getJointInfo(body_id, joint_ids)[-4])
return np.asarray([self.sim.getJointInfo(body_id, joint_id)[-4] for joint_id in joint_ids])
def set_joint_positions(self, body_id, joint_ids, positions, velocities=None, kps=None, kds=None, forces=None):
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).
@@ -2230,7 +2230,7 @@ class Bullet(Simulator):
self.set_joint_motor_control(body_id, joint_ids, control_mode=pybullet.POSITION_CONTROL, positions=positions,
velocities=velocities, forces=forces, kp=kps, kd=kds)
def get_joint_positions(self, body_id, joint_ids):
def _get_joint_positions(self, body_id, joint_ids):
"""
Get the position of the given joint(s).
@@ -2248,7 +2248,7 @@ class Bullet(Simulator):
return self.sim.getJointState(body_id, joint_ids)[0]
return np.asarray([state[0] for state in self.sim.getJointStates(body_id, joint_ids)])
def set_joint_velocities(self, body_id, joint_ids, velocities, max_force=None):
def _set_joint_velocities(self, body_id, joint_ids, velocities, max_force=None):
"""
Set the velocity of the given joint(s) (using velocity control).
@@ -2269,7 +2269,7 @@ class Bullet(Simulator):
self.sim.setJointMotorControlArray(body_id, joint_ids, self.sim.VELOCITY_CONTROL,
targetVelocities=velocities, forces=max_force)
def get_joint_velocities(self, body_id, joint_ids):
def _get_joint_velocities(self, body_id, joint_ids):
"""
Get the velocity of the given joint(s).
@@ -2372,7 +2372,7 @@ class Bullet(Simulator):
# q_idx = self.get_q_indices(body_id, joint_ids)
# return accelerations[q_idx]
def set_joint_torques(self, body_id, joint_ids, torques):
def _set_joint_torques(self, body_id, joint_ids, torques):
"""
Set the torque/force to the given joint(s) (using force/torque control).
@@ -2385,7 +2385,7 @@ class Bullet(Simulator):
self.sim.setJointMotorControl2(body_id, joint_ids, self.sim.TORQUE_CONTROL, force=torques)
self.sim.setJointMotorControlArray(body_id, joint_ids, self.sim.TORQUE_CONTROL, forces=torques)
def get_joint_torques(self, body_id, joint_ids):
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
@@ -3397,8 +3397,8 @@ class Bullet(Simulator):
Args:
body_id (int): unique body id.
link_id (int): link id.
local_position (np.array[float[3]]): the point on the specified link to compute the Jacobian (in link local
coordinates around its center of mass). If None, it will use the CoM position (in the link frame).
local_position (np.array[float[3]]): the point on the specified link to compute the Jacobian (in link
local coordinates around its center of mass). If None, it will use the CoM position (in the link frame).
q (np.array[float[N]]): joint positions of size N, where N is the number of DoFs.
dq (np.array[float[N]]): joint velocities of size N, where N is the number of DoFs.
des_ddq (np.array[float[N]]): desired joint accelerations of size N.
+17 -17
View File
@@ -231,13 +231,13 @@ class BulletROS(Bullet): # , ROS):
Args:
body_id (int): unique body id.
joint_ids (int, list of int): joint id, or list of joint ids.
joint_ids (int, list[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]
np.array[float[N]]: joint positions [rad]
"""
if body_id in self.subscribers:
q = self.subscribers[body_id].get_joint_positions(joint_ids)
@@ -258,12 +258,12 @@ class BulletROS(Bullet): # , ROS):
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.
joint_ids (int, list[int]): joint id, or list of joint ids.
positions (float, np.array[float[N]]): desired position, or list of desired positions [rad]
velocities (None, float, np.array[float[N]]): desired velocity, or list of desired velocities [rad/s]
kps (None, float, np.array[float[N]]): position gain(s)
kds (None, float, np.array[float[N]]): velocity gain(s)
forces (None, float, np.array[float[N]]): maximum motor force(s)/torque(s) used to reach the target values.
"""
super(BulletROS, self).set_joint_positions(body_id, joint_ids, positions, velocities, kps, kds, forces)
if body_id in self.publishers:
@@ -276,13 +276,13 @@ class BulletROS(Bullet): # , ROS):
Args:
body_id (int): unique body id.
joint_ids (int, list of int): joint id, or list of joint ids.
joint_ids (int, list[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]
np.array[float[N]]: joint velocities [rad/s]
"""
if body_id in self.subscribers:
dq = self.subscribers[body_id].get_joint_velocities(joint_ids)
@@ -303,9 +303,9 @@ class BulletROS(Bullet): # , ROS):
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
joint_ids (int, list[int]): joint id, or list of joint ids.
velocities (float, np.array[float[N]]): desired velocity, or list of desired velocities [rad/s]
max_force (None, float, np.array[float[N]]): maximum motor forces/torques
"""
super(BulletROS, self).set_joint_velocities(body_id, joint_ids, velocities, max_force)
if body_id in self.publishers:
@@ -320,13 +320,13 @@ class BulletROS(Bullet): # , ROS):
Args:
body_id (int): unique body id.
joint_ids (int, list of int): a joint id, or list of joint ids.
joint_ids (int, list[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]
np.array[float[N]]: torques associated to the given joints [Nm]
"""
if body_id in self.subscribers:
torques = self.subscribers[body_id].get_joint_torques(joint_ids)
@@ -347,8 +347,8 @@ class BulletROS(Bullet): # , ROS):
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].
joint_ids (int, list[int]): joint id, or list of joint ids.
torques (float, list[float]): desired torque(s) to apply to the joint(s) [N].
"""
super(BulletROS, self).set_joint_torques(body_id, joint_ids, torques)
if body_id in self.publishers:
@@ -0,0 +1,14 @@
# import middleware abstract class
from .middleware import MiddleWare
# import ROS
from .ros import ROS
# # define decorator
# def middleware(function):
# def wrapper(self, *args, **kwargs):
# if self.middleware is None:
# return function(*args, **kwargs)
# return wrapper
@@ -46,6 +46,10 @@ class MiddleWare(object):
self.publish = publish
self.teleoperate = teleoperate
##############
# Properties #
##############
@property
def subscribe(self):
return self._subscribe
@@ -69,3 +73,178 @@ class MiddleWare(object):
@teleoperate.setter
def teleoperate(self, teleoperate):
self._teleoperate = bool(teleoperate)
###########
# Methods #
###########
def has_sensor(self, body_id, name):
"""
Check if the specified robot has the given sensor.
Args:
body_id (int): body unique id.
name (str): name of the sensor.
Returns:
bool: True if the specified robot has the given sensor.
"""
pass
def get_sensor_values(self, body_id, name):
"""
Return the sensor.
Args:
body_id (int): body unique id.
name (str): name of the sensor.
Returns:
np.array, dict, list, None: sensor values. None if it didn't have anything.
"""
pass
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[int]): joint id, or list of joint ids.
Returns:
if 1 joint:
float: joint position [rad]
if multiple joints:
np.array[float[N]]: joint positions [rad]
"""
pass
def set_joint_positions(self, body_id, joint_ids, positions, velocities=None, kps=None, kds=None, forces=None,
check_teleoperate=False):
"""
Set the position of the given joint(s) (using position control).
Args:
body_id (int): unique body id.
joint_ids (int, list[int]): joint id, or list of joint ids.
positions (float, np.array[float[N]]): desired position, or list of desired positions [rad]
velocities (None, float, np.array[float[N]]): desired velocity, or list of desired velocities [rad/s]
kps (None, float, np.array[float[N]]): position gain(s)
kds (None, float, np.array[float[N]]): velocity gain(s)
forces (None, float, np.array[float[N]]): maximum motor force(s)/torque(s) used to reach the target values.
check_teleoperate (bool): if True, it will check if the given `teleoperate` argument has been set to True,
and if so, it will set the joint positions. If the `teleoperate` argument has been set to False, it
won't set the joint positions.
"""
pass
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[int]): joint id, or list of joint ids.
Returns:
if 1 joint:
float: joint velocity [rad/s]
if multiple joints:
np.array[float[N]]: joint velocities [rad/s]
"""
pass
def set_joint_velocities(self, body_id, joint_ids, velocities, max_force=None, check_teleoperate=False):
"""
Set the velocity of the given joint(s) (using velocity control).
Args:
body_id (int): unique body id.
joint_ids (int, list[int]): joint id, or list of joint ids.
velocities (float, np.array[float[N]]): desired velocity, or list of desired velocities [rad/s]
max_force (None, float, np.array[float[N]]): maximum motor forces/torques.
check_teleoperate (bool): if True, it will check if the given `teleoperate` argument has been set to True,
and if so, it will set the joint velocities. If the `teleoperate` argument has been set to False, it
won't set the joint velocities.
"""
pass
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[int]): a joint id, or list of joint ids.
Returns:
if 1 joint:
float: torque [Nm]
if multiple joints:
np.array[float[N]]: torques associated to the given joints [Nm]
"""
pass
def set_joint_torques(self, body_id, joint_ids, torques, check_teleoperate=False):
"""
Set the torque/force to the given joint(s) (using force/torque control).
Args:
body_id (int): unique body id.
joint_ids (int, list[int]): joint id, or list of joint ids.
torques (float, list[float]): desired torque(s) to apply to the joint(s) [N].
check_teleoperate (bool): if True, it will check if the given `teleoperate` argument has been set to True,
and if so, it will set the joint torques. If the `teleoperate` argument has been set to False, it won't
set the joint torques.
"""
pass
def get_jacobian(self, body_id, link_id, local_position=None, q=None):
r"""
Return the full geometric Jacobian matrix :math:`J(q) = [J_{lin}(q), J_{ang}(q)]^T`, such that:
.. math:: v = [\dot{p}, \omega]^T = J(q) \dot{q}
where :math:`\dot{p}` is the Cartesian linear velocity of the link, and :math:`\omega` is its angular velocity.
Warnings: if we have a floating base then the Jacobian will also include columns corresponding to the root
link DoFs (at the beginning). If it is a fixed base, it will only have columns associated with the joints.
Args:
body_id (int): unique body id.
link_id (int): link id.
local_position (np.array[float[3]]): the point on the specified link to compute the Jacobian (in link local
coordinates around its center of mass). If None, it will use the CoM position (in the link frame).
q (np.array[float[N]]): joint positions of size N, where N is the number of DoFs.
Returns:
np.array[float[6,N]], np.array[float[6,6+N]]: full geometric (linear and angular) Jacobian matrix. The
number of columns depends if the base is fixed or floating.
"""
pass
def get_inertia_matrix(self, body_id, q):
r"""
Return the mass/inertia matrix :math:`H(q)`, which is used in the rigid-body equation of motion (EoM) in joint
space given by (see [1]):
.. math:: \tau = H(q)\ddot{q} + C(q,\dot{q})
where :math:`\tau` is the vector of applied torques, :math:`H(q)` is the inertia matrix, and
:math:`C(q,\dot{q}) \dot{q}` is the vector accounting for Coriolis, centrifugal forces, gravity, and any
other forces acting on the system except the applied torques :math:`\tau`.
Warnings: If the base is floating, it will return a [6+N,6+N] inertia matrix, where N is the number of actuated
joints. If the base is fixed, it will return a [N,N] inertia matrix
Args:
body_id (int): body unique id.
q (np.array[float[N]]): joint positions of size N, where N is the total number of DoFs.
Returns:
np.array[float[N,N]], np.array[float[6+N,6+N]]: inertia matrix
"""
pass
+712 -52
View File
@@ -1,8 +1,35 @@
#!/usr/bin/env python
"""Define the ROS middleware API.
ROS (Robot Operating System) [1] is a robotics middleware which "provides libraries and tools to help software
developers create robot applications. It provides hardware abstraction, device drivers, libraries, visualizers,
message-passing, package management, and more."
- rospy [2]: "rospy is a pure Python client library for ROS. The rospy client API enables Python programmers to
quickly interface with ROS Topics, Services, and Parameters."
- ros_control [3]: "A set of packages that include controller interfaces, controller managers, transmissions and
hardware_interfaces."
- robot_state_publisher [4]: "This package allows you to publish the state of a robot to tf. Once the state gets
published, it is available to all components in the system that also use tf. The package takes the joint angles of
the robot as input and publishes the 3D poses of the robot links, using a kinematic tree model of the robot. The
package can both be used as a library and as a ROS node."
- joint_state_publisher [5]: "This package contains a tool for setting and publishing joint state values for a given
URDF."
Note to compile ROS packages using `catkin_make`, you might have to specify the Python version used using:
- catkin_make -DPYTHON_EXECUTABLE=path/to/bin/python3
Dependencies in PRL:
* `pyrobolearn.simulators.middlewares.middleware.MiddleWare`
References:
- [1] ROS: http://www.ros.org/ and http://wiki.ros.org
- [2] rospy: http://wiki.ros.org/rospy
- [3] ros_control: http://wiki.ros.org/ros_control
- ROS control an overview: https://roscon.ros.org/2014/wp-content/uploads/2014/07/ros_control_an_overview.pdf
- [4] robot_state_publisher: http://wiki.ros.org/robot_state_publisher
- [5] joint_state_publisher: http://wiki.ros.org/joint_state_publisher
- [6] roslaunch: http://wiki.ros.org/roslaunch/API%20Usage
"""
# TODO
@@ -12,8 +39,21 @@ import psutil
import signal
import importlib
import inspect
import yaml
import rospy
import rostopic
import controller_manager.controller_manager_interface as cm_interface
import roslaunch
import rosparam
import std_msgs.msg as std_msg
import sensor_msgs.msg as sensor_msg
import gazebo_msgs.msg as gazebo_msg
import geometry_msgs.msg as geometry_msg
from pyrobolearn.simulators.middlewares.middleware import MiddleWare
from pyrobolearn.simulators.middlewares.ros_publisher import Publisher, PublisherData, RobotPublisher
from pyrobolearn.simulators.middlewares.ros_subscriber import Subscriber, SubscriberData, RobotSubscriber
__author__ = "Brian Delhaisse"
@@ -26,6 +66,123 @@ __email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class RobotMiddleWare(object):
r"""Robot middleware interface.
"""
def __init__(self, robot_id, urdf=None, subscribe=False, publish=False, teleoperate=False, control_file=None):
"""
Initialize the robot middleware interface.
Args:
robot_id (int): robot unique id.
urdf (str): path to the URDF file.
subscribe (bool): if True, it will subscribe to the topics associated to the loaded robot, and will read
the values published on these topics.
publish (bool): if True, it will publish the given values to the topics associated to the loaded robot.
teleoperate (bool): if True, it will move the robot based on the received or sent values based on the 2
previous attributes :attr:`subscribe` and :attr:`publish`.
control_file (str): path to the YAML control file.
"""
self.id = robot_id
self.urdf = urdf
self.control_file = control_file
self.subscribers = {}
self.publishers = {}
# set variables
self.subscribe = subscribe
self.publish = publish
self.teleoperate = teleoperate
def get_joint_positions(self, joint_ids):
"""
Get the position of the given joint(s).
Args:
joint_ids (int, list[int]): joint id, or list of joint ids.
Returns:
if 1 joint:
float: joint position [rad]
if multiple joints:
np.array[float[N]]: joint positions [rad]
"""
q = self.subscribers[body_id].get_joint_positions(joint_ids)
if self.teleoperate and body_id in self.publishers:
self.publishers[body_id].set_joint_positions(joint_ids, q)
self.publishers[body_id].publish('joint_states')
return q
def set_joint_positions(self, joint_ids, positions, velocities=None, kps=None, kds=None, forces=None):
"""
Set the position of the given joint(s) (using position control).
Args:
joint_ids (int, list[int]): joint id, or list of joint ids.
positions (float, np.array[float[N]]): desired position, or list of desired positions [rad]
velocities (None, float, np.array[float[N]]): desired velocity, or list of desired velocities [rad/s]
kps (None, float, np.array[float[N]]): position gain(s)
kds (None, float, np.array[float[N]]): velocity gain(s)
forces (None, float, np.array[float[N]]): maximum motor force(s)/torque(s) used to reach the target values.
"""
pass
def get_joint_velocities(self, joint_ids):
"""
Get the velocity of the given joint(s).
Args:
joint_ids (int, list[int]): joint id, or list of joint ids.
Returns:
if 1 joint:
float: joint velocity [rad/s]
if multiple joints:
np.array[float[N]]: joint velocities [rad/s]
"""
pass
def set_joint_velocities(self, joint_ids, velocities, max_force=None):
"""
Set the velocity of the given joint(s) (using velocity control).
Args:
joint_ids (int, list[int]): joint id, or list of joint ids.
velocities (float, np.array[float[N]]): desired velocity, or list of desired velocities [rad/s]
max_force (None, float, np.array[float[N]]): maximum motor forces/torques.
"""
pass
def get_joint_torques(self, 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:
joint_ids (int, list[int]): a joint id, or list of joint ids.
Returns:
if 1 joint:
float: torque [Nm]
if multiple joints:
np.array[float[N]]: torques associated to the given joints [Nm]
"""
pass
def set_joint_torques(self, joint_ids, torques):
"""
Set the torque/force to the given joint(s) (using force/torque control).
Args:
joint_ids (int, list[int]): joint id, or list of joint ids.
torques (float, list[float]): desired torque(s) to apply to the joint(s) [N].
"""
pass
class ROS(MiddleWare):
r"""ROS Interface middleware
@@ -60,29 +217,24 @@ class ROS(MiddleWare):
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._robots = {} # {body_id: RobotMiddleware}
self.count_id = -1
def close(self):
"""
Close everything
"""
# delete each subscribers
# roslaunch
self.launch = roslaunch.scriptapi.ROSLaunch()
self.launch.start()
# delete each publishers
self.processes = {} # {Node: process}
# delete ROS
if self.roscore is not None:
os.killpg(os.getpgid(self.roscore.pid), signal.SIGTERM)
##############
# Properties #
##############
@property
def is_subscribing(self):
@@ -94,6 +246,376 @@ class ROS(MiddleWare):
"""Return True if we are publishing to topics."""
return self.publish
###########
# Methods #
###########
##############
# ROS launch #
##############
@staticmethod
def create_ros_node(package, node_type, name=None, namespace='/', machine_name=None, args='', respawn=False,
respawn_delay=0.0, remap_args=None, env_args=None, output=None, cwd=None, launch_prefix=None,
required=False, filename='<unknown>'):
"""
Create a ROS node; data structure for storing information about a desired node in the ROS system Corresponds
to the 'node' tag in the launch specification.
This basically wraps the `roslaunch.core.Node(...)`.
Args:
package (str): node package name.
node_type (str): node type.
name (str): node name.
namespace (str): namespace for node.
machine_name (str): name of machine to run node on.
args (str): argument string to pass to node executable.
respawn (bool): if True, respawn node if it dies.
respawn_delay (float): if respawn is True, respawn node after delay.
remap_args (list[tuple[str,str]]): list of [(from, to)] remapping arguments.
env_args (list[tuple[str,str]]): list of [(key, value)] of additional environment vars to set for node.
output (str): where to log output to, either Node, 'screen' or 'log'.
cwd (str): current working directory of node, either 'node', 'ROS_HOME'. Default: ROS_HOME.
launch_prefix (str): launch command/arguments to prepend to node executable arguments.
required (bool): node is required to stay running (launch fails if node dies).
filename (str): name of file Node was parsed from.
Raises:
ValueError: if parameters do not validate.
Returns:
roslaunch.core.Node: ROS node data structure.
"""
return roslaunch.core.Node(package=package, node_type=node_type, name=name, namespace=namespace,
machine_name=machine_name, args=args, respawn=respawn, respawn_delay=respawn_delay,
remap_args=remap_args, env_args=env_args, output=output, cwd=cwd,
launch_prefix=launch_prefix, required=required, filename=filename)
def launch(self, ros_node):
"""
Launch a ROS node.
Args:
ros_node (roslaunch.core.Node): ROS node.
Returns:
roslaunch.nodeprocess.LocalProcess: the process.
"""
process = self.launch.launch(ros_node)
self.processes[ros_node] = process
return process
@staticmethod
def get_namespace():
"""
Get the namespace.
Returns:
str: namespace.
"""
return rospy.get_namespace()
################################################
# ROS Topics/Services + Publishers/Subscribers #
################################################
def add_subscriber(self, body_id, topic_name, freq, fct, attribute):
pass
def add_publisher(self, body_id, topic_name, freq, fct, attribute):
pass
def remap(self, topic1, topic2, freq):
pass
def remove_publisher(self, publisher_id):
pass
def remove_subscriber(self, subscriber_id):
pass
@staticmethod
def get_topics(namespace='/'):
"""
Get the published topics.
Returns:
list[list[str, str]]: list of tuples where the first is the topic and the second element is the message
type that topic accepts.
"""
return rospy.get_published_topics(namespace=namespace)
@staticmethod
def has_topic(name, namespace='/'):
"""
Check if the specified topic has been advertised.
Warnings: this has a O(N) time complexity.
Args:
name (str): name of the topic.
namespace (str): namespace.
Returns:
bool: True if the topic has been advertised.
"""
topics = rospy.get_published_topics(namespace=namespace)
for topic_name, dtype in topics:
if topic_name == name: # or topic_name.split('/')[-1] == name:
return True
return False
@staticmethod
def get_topic_type(name):
"""
Get the topic type name of the given topic name.
This is the same as typing the following in the terminal:
- `rostopic type /topic_name`
Args:
name (str): name of the topic.
Returns:
str: type of the topic
"""
return rostopic.get_topic_type(name)[0]
@staticmethod
def get_topic_class(name):
"""
Get the topic class type of the given topic name.
Args:
name (str): name of the topic.
Returns:
type: topic class type. This can later be instantiated.
"""
return rostopic.get_topic_class(name)
###########################
# ROS parameters (+ YAML) #
###########################
@staticmethod
def load_parameter_file(filename, namespace=None):
"""
Load a parameter YAML file.
Args:
filename (str): path to the YAML file.
namespace (str): default namespace.
Returns:
list[dict[str,dict[str:dict[str:dict]]], str]: [{robot_name: {param_name: value}}, namespace]
"""
return rosparam.load_file(filename, default_namespace=namespace)
@staticmethod
def load_parameter_string(parameters, namespace=None):
"""
Load a control configuration YAML string.
Args:
parameters (str): string in the YAML format specifying the control parameters.
namespace (str): default namespace.
Returns:
list[dict[str,dict[str:dict[str:dict]]], str]: [{robot_name: {param_name: value}}, namespace]
"""
return rosparam.load_str(parameters, default_namespace=namespace)
@staticmethod
def set_parameter(name, value):
"""
Set a parameter on the param server.
Args:
name (str): name of the parameter.
value (str, dict): parameter value. "If param_value is a dictionary it will be treated as a parameter
tree, where param_name is the namespace. For example::: {'x':1,'y':2,'sub':{'z':3}} will set
`name/x=1`, `name/y=2`, and `name/sub/z=3`. Furthermore, it will replace all existing parameters in
the `name` namespace with the parameters in `value`. You must set parameters individually if you wish
to perform a union update.
"""
rospy.set_param(name, value)
@staticmethod
def get_parameter(name, default=None):
r"""
Get the parameter associated with the given name from the param server.
Args:
name (str): name of the parameter.
default (object): default value to return if the parameter is not found.
Returns:
str: parameter value
"""
return rospy.get_param(name, default=default)
@staticmethod
def get_parameter_names():
"""
Return the parameter names that have been loaded.
Returns:
list[str]: list of parameter names.
"""
return rospy.get_param_names()
@staticmethod
def upload_parameters(values, namespace='/'):
"""
Upload parameters to the Parameter Server.
Args:
values (dict): dictionary where keys are parameter names and values are parameter values.
namespace (str): namespace to load parameters.
"""
rosparam.upload_params(ns=namespace, values=values)
@staticmethod
def load_config_file(filename, namespace=None):
"""
Load a control configuration YAML file.
Args:
filename (str): path to the YAML file.
namespace (str): default namespace.
Returns:
list[dict[str,dict[str:dict[str:dict]]], str]: [{robot_name: {param_name: value}}, namespace]
"""
params = rosparam.load_file(filename, default_namespace=namespace)
params = params[0]
namespace = params[1] + 'rrbot/'
params = params[0]['rrbot']
for key, value in params.items():
rosparam.upload_params(ns=namespace + key, values=value)
@staticmethod
def load_config_string(parameters, namespace=None):
"""
Load a control configuration YAML string.
Args:
parameters (str): string in the YAML format specifying the control parameters.
namespace (str): default namespace.
Returns:
list[list[str,str]]:
"""
params = rosparam.load_str(parameters, default_namespace=namespace)
###############
# ROS CONTROL #
###############
def get_pid(self, body_id):
pass
def set_pid(self, body_id, p=None, i=None, d=None):
pass
@staticmethod
def load_controller(name):
"""
Load controller based on the given name.
Args:
name (str): name of the controller to load.
Returns:
bool: True if we could load the controller.
"""
cm_interface.load_controller(name)
@staticmethod
def unload_controller(name):
"""
Unload the controller based on the given name.
Args:
name (str): name of the controller to unload.
Returns:
bool: True if we could unload the controller.
"""
cm_interface.unload_controller(name)
@staticmethod
def start_controller(name):
"""
Start the controller based on the given name.
Args:
name (str): name of the controller to start.
Returns:
bool: True if we could start the controller.
"""
cm_interface.start_controller(name)
@staticmethod
def stop_controller(name):
"""
Stop the controller based on the given name.
Args:
name (str): name of the controller to stop.
Returns:
bool: True if we could stop the controller.
"""
return cm_interface.stop_controller(name)
@staticmethod
def get_controllers():
"""
Return the list of controllers.
"""
rospy.wait_for_service('controller_manager/list_controllers')
s = rospy.ServiceProxy('controller_manager/list_controllers', cm_interface.ListControllers)
resp = s.call(cm_interface.ListControllersRequest())
return [(c.name, list(set(r.hardware_interface for r in c.claimed_resources)), c.state)
for c in resp.controller]
@staticmethod
def get_controller_types():
"""
Return the list of controller types.
"""
rospy.wait_for_service('controller_manager/list_controller_types')
s = rospy.ServiceProxy('controller_manager/list_controller_types', cm_interface.ListControllerTypes)
resp = s.call(cm_interface.ListControllerTypesRequest())
return [t for t in resp.types]
##############
# Middleware #
##############
def close(self):
"""
Close everything
"""
# delete each subscribers
# delete each publishers
for process in self.processes.values():
process.stop()
# stop launch
if self.launch:
self.launch.stop()
# delete ROS
if self.roscore is not None:
os.killpg(os.getpgid(self.roscore.pid), signal.SIGTERM)
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.
@@ -106,9 +628,10 @@ class ROS(MiddleWare):
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]
position (np.array[float[3]]): create the base of the object at the specified position in world space
coordinates [x,y,z]
orientation (np.array[float[4]]): 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
@@ -171,6 +694,27 @@ class ROS(MiddleWare):
if self.publish:
check_ros('publisher', self.publishers, id_)
# get path to the URDF folder
path = os.path.abspath(filename) # /path/to/pyrobolearn/robots/urdfs/<robot>/robot.urdf
dirname = str(os.path.dirname(path)) # /path/to/pyrobolearn/robots/urdfs/<robot>/
basename = str(os.path.basename(path).split('.')[-2]) # robot name without extension
config_file = dirname + basename + ".yaml"
# check for YAML control configuration file
if os.path.isfile(config_file):
# if it exists, import it
data = yaml.load(open(config_file, 'r'), Loader=yaml.FullLoader)
else:
data = {basename: {'joint_state_controller': {'publish_rate': 50,
'type': 'joint_state_controller/JointStateController'}}}
node = roslaunch.core.Node(package="controller_manager", name="controller_spawner", node_type="spawner",
respawn="false", output="screen", namespace="/rrbot", args=' '.join(
["joint_state_controller", "joint1_position_controller", "joint2_position_controller"]))
# create subscribers
return id_
def get_joint_positions(self, body_id, joint_ids):
@@ -179,33 +723,41 @@ class ROS(MiddleWare):
Args:
body_id (int): unique body id.
joint_ids (int, list of int): joint id, or list of joint ids.
joint_ids (int, list[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]
np.array[float[N]]: joint positions [rad]
"""
if body_id in self.subscribers:
return self.subscribers[body_id].get_joint_positions(joint_ids)
robot = self._robots.get(body_id)
if robot is not None:
return robot.get_joint_positions(joint_ids)
def set_joint_positions(self, body_id, joint_ids, positions, velocities=None, kps=None, kds=None, forces=None):
def set_joint_positions(self, body_id, joint_ids, positions, velocities=None, kps=None, kds=None, forces=None,
check_teleoperate=False):
"""
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.
joint_ids (int, list[int]): joint id, or list of joint ids.
positions (float, np.array[float[N]]): desired position, or list of desired positions [rad]
velocities (None, float, np.array[float[N]]): desired velocity, or list of desired velocities [rad/s]
kps (None, float, np.array[float[N]]): position gain(s)
kds (None, float, np.array[float[N]]): velocity gain(s)
forces (None, float, np.array[float[N]]): maximum motor force(s)/torque(s) used to reach the target values.
check_teleoperate (bool): if True, it will check if the given `teleoperate` argument has been set to True,
and if so, it will set the joint positions. If the `teleoperate` argument has been set to False, it
won't set the joint positions.
"""
if body_id in self.publishers:
self.publishers[body_id].set_joint_positions(joint_ids, positions)
self.publishers[body_id].publish('joint_states')
robot = self._robots.get(body_id)
if robot is not None:
if check_teleoperate and not self.teleoperate:
return None
return robot.set_joint_positions(joint_ids, positions, velocities=velocities, kps=kps, kds=kds,
forces=forces)
def get_joint_velocities(self, body_id, joint_ids):
"""
@@ -213,30 +765,36 @@ class ROS(MiddleWare):
Args:
body_id (int): unique body id.
joint_ids (int, list of int): joint id, or list of joint ids.
joint_ids (int, list[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]
np.array[float[N]]: joint velocities [rad/s]
"""
if body_id in self.subscribers:
return self.subscribers[body_id].get_joint_velocities(joint_ids)
robot = self._robots.get(body_id)
if robot is not None:
return robot.get_joint_velocities(joint_ids)
def set_joint_velocities(self, body_id, joint_ids, velocities, max_force=None):
def set_joint_velocities(self, body_id, joint_ids, velocities, max_force=None, check_teleoperate=False):
"""
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
joint_ids (int, list[int]): joint id, or list of joint ids.
velocities (float, np.array[float[N]]): desired velocity, or list of desired velocities [rad/s]
max_force (None, float, np.array[float[N]]): maximum motor forces/torques.
check_teleoperate (bool): if True, it will check if the given `teleoperate` argument has been set to True,
and if so, it will set the joint velocities. If the `teleoperate` argument has been set to False, it
won't set the joint velocities.
"""
if body_id in self.publishers:
self.publishers[body_id].set_joint_velocities(joint_ids, velocities)
self.publishers[body_id].publish('joint_states')
robot = self._robots.get(body_id)
if robot is not None:
if check_teleoperate and not self.teleoperate:
return None
return robot.set_joint_velocities(joint_ids, velocities, max_force=max_force)
def get_joint_torques(self, body_id, joint_ids):
"""
@@ -246,26 +804,128 @@ class ROS(MiddleWare):
Args:
body_id (int): unique body id.
joint_ids (int, list of int): a joint id, or list of joint ids.
joint_ids (int, list[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]
np.array[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)
robot = self._robots.get(body_id)
if robot is not None:
return robot.get_joint_torques(joint_ids)
def set_joint_torques(self, body_id, joint_ids, torques):
def set_joint_torques(self, body_id, joint_ids, torques, check_teleoperate=False):
"""
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].
joint_ids (int, list[int]): joint id, or list of joint ids.
torques (float, list[float]): desired torque(s) to apply to the joint(s) [N].
check_teleoperate (bool): if True, it will check if the given `teleoperate` argument has been set to True,
and if so, it will set the joint torques. If the `teleoperate` argument has been set to False, it won't
set the joint torques.
"""
if body_id in self.publishers:
self.publishers[body_id].set_joint_torques(joint_ids, torques)
self.publishers[body_id].publish('joint_states')
robot = self._robots.get(body_id)
if robot is not None:
if check_teleoperate and not self.teleoperate:
return None
return robot.set_joint_torques(joint_ids, torques)
def get_jacobian(self, body_id, link_id, local_position=None, q=None):
r"""
Return the full geometric Jacobian matrix :math:`J(q) = [J_{lin}(q), J_{ang}(q)]^T`, such that:
.. math:: v = [\dot{p}, \omega]^T = J(q) \dot{q}
where :math:`\dot{p}` is the Cartesian linear velocity of the link, and :math:`\omega` is its angular velocity.
Warnings: if we have a floating base then the Jacobian will also include columns corresponding to the root
link DoFs (at the beginning). If it is a fixed base, it will only have columns associated with the joints.
Args:
body_id (int): unique body id.
link_id (int): link id.
local_position (np.array[float[3]]): the point on the specified link to compute the Jacobian (in link local
coordinates around its center of mass). If None, it will use the CoM position (in the link frame).
q (np.array[float[N]]): joint positions of size N, where N is the number of DoFs.
Returns:
np.array[float[6,N]], np.array[float[6,6+N]]: full geometric (linear and angular) Jacobian matrix. The
number of columns depends if the base is fixed or floating.
"""
pass
def get_inertia_matrix(self, body_id, q):
r"""
Return the mass/inertia matrix :math:`H(q)`, which is used in the rigid-body equation of motion (EoM) in joint
space given by (see [1]):
.. math:: \tau = H(q)\ddot{q} + C(q,\dot{q})
where :math:`\tau` is the vector of applied torques, :math:`H(q)` is the inertia matrix, and
:math:`C(q,\dot{q}) \dot{q}` is the vector accounting for Coriolis, centrifugal forces, gravity, and any
other forces acting on the system except the applied torques :math:`\tau`.
Warnings: If the base is floating, it will return a [6+N,6+N] inertia matrix, where N is the number of actuated
joints. If the base is fixed, it will return a [N,N] inertia matrix
Args:
body_id (int): body unique id.
q (np.array[float[N]]): joint positions of size N, where N is the total number of DoFs.
Returns:
np.array[float[N,N]], np.array[float[6+N,6+N]]: inertia matrix
"""
pass
def has_sensor(self, body_id, name):
"""
Check if the specified robot has the given sensor.
Args:
body_id (int): body unique id.
name (str): name of the sensor.
Returns:
bool: True if the specified robot has the given sensor.
"""
robot = self._robots.get(body_id)
if robot is not None:
return robot.has_sensor(name)
return False
def get_sensor_values(self, body_id, name):
"""
Return the sensor.
Args:
body_id (int): body unique id.
name (str): name of the sensor.
Returns:
np.array, dict, list, None: sensor values. None if it didn't have anything.
"""
robot = self._robots.get(body_id)
if robot is not None:
return robot.get_sensor_values(name)
def can_teleoperate(self, body_id):
robot = self._robots.get(body_id, None)
if robot is None:
return False
return robot.teleoperate
def can_subscribe(self, body_id):
robot = self._robots.get(body_id, None)
if robot is None:
return False
return robot.subscribe
def can_publish(self, body_id):
robot = self._robots.get(body_id, None)
if robot is None:
return False
return robot.publish
@@ -0,0 +1,152 @@
#!/usr/bin/env python
"""Define the abstract robot publisher.
"""
import rospy
# import the messages
from std_msgs import msg as std_msg
from sensor_msgs import msg as sensor_msg
from geometry_msgs import msg as geometry_msg
__author__ = "Brian Delhaisse"
__copyright__ = "Copyright 2018, PyRoboLearn"
__credits__ = ["Brian Delhaisse"]
__license__ = "GNU GPLv3"
__version__ = "1.0.0"
__maintainer__ = "Brian Delhaisse"
__email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class PublisherData(object):
r"""Publisher data holder
"""
def __init__(self, topic, data_class, queue_size=10):
self.__dict__['publisher'] = rospy.Publisher(topic, data_class, queue_size=queue_size)
self.__dict__['attributes'] = [attr for attr in [attr for attr in dir(data_class) if not attr.startswith('_')]
if not callable(getattr(data_class, attr))]
self.__dict__['publisher_data'] = data_class()
def publish(self, data=None):
if data is None:
self.publisher.publish(self.publisher_data)
else:
self.publisher.publish(data)
def __setattr__(self, key, value):
if key in self.attributes:
setattr(self.publisher_data, key, value)
def __getattr__(self, key):
return getattr(self.publisher_data, key)
class Publisher(object):
r"""Publisher class
This Publisher abstract class is the class from which all the other publishers inherit from. It provides the
common functionalities between the various publishers.
"""
def __init__(self, publisher_id=None):
"""
Initialize the publisher.
Args:
publisher_id (int, None): publisher id which is used when initializing the node. If None, a name will be
auto-generated for the name using name as the base. See the documentation for the :attr:`anonymous`
parameter in `rospy.init_node`.
"""
# initialize the node
if publisher_id is None:
rospy.init_node(self.__class__.__name__, anonymous=True)
else:
rospy.init_node(self.__class__.__name__ + str(publisher_id))
# all publishers
self.publishers = dict()
def create_publisher(self, name, topic, data_class):
"""
Create a publisher to the specific topic.
Args:
name (str): unique name of the publisher. The name must be unique. You will be able to access to this
topic (str): name of the topic.
data_class (object): data type class to use for messages
Returns:
PublisherData: the publisher data holder.
"""
publisher = PublisherData(topic, data_class)
self.publishers[name] = publisher
setattr(self, name, publisher)
return publisher
def publish(self, name=None, data=None):
if name is None and data is None:
for publisher in self.publishers.values():
publisher.publish()
elif name is not None:
self.__dict__[name].publish(data)
# def __getattr__(self, name):
# return self.publishers[name]
class RobotPublisher(Publisher):
r"""Robot Publisher class
This Robot Publisher class is the class from which all the robot publishers inherit from.
"""
def __init__(self, name, id_=None):
r"""
Initialize the robot publisher.
Args:
name (str): name of the robot. This will be used to create the topics.
id_ (int, None): robot id which is used when initializing the node. If None, a name will be
auto-generated for the name using name as the base. See the documentation for the :attr:`anonymous`
parameter in `rospy.init_node`.
"""
super(RobotPublisher, self).__init__(publisher_id=id_)
self.name = name.lower()
# create Joint states
# self.create_publisher('joint_states', self.name + '/joint_states', sensor_msg.JointState)
self.joint_states = PublisherData(self.name + '/joint_states', sensor_msg.JointState)
self.publishers['joint_states'] = self.joint_states
def set_joint_positions(self, joint_ids, positions):
# self.joint_states.position[joint_ids] = positions
self.joint_states.position = positions
def set_joint_velocities(self, joint_ids, velocities):
self.joint_states.velocity[joint_ids] = velocities
def set_joint_torques(self, joint_ids, torques):
self.joint_states.effort[joint_ids] = torques
# Tests
if __name__ == '__main__':
# NOTE: run roscore before hand
import numpy as np
from itertools import count
import time
publisher = RobotPublisher('walter')
print("Published topics: {}".format(rospy.get_published_topics()))
print("Robot joint state attributes: {}".format(publisher.joint_states.attributes))
publisher.joint_states.position = np.array(range(3))
for t in count():
print(t)
publisher.publish()
time.sleep(0.1)
@@ -0,0 +1,154 @@
#!/usr/bin/env python
"""Define the abstract robot subscriber.
"""
import numpy as np
import rospy
# import the messages
# from std_msgs import msg as std_msg
from sensor_msgs import msg as sensor_msg
# from geometry_msgs import msg as geometry_msg
__author__ = "Brian Delhaisse"
__copyright__ = "Copyright 2018, PyRoboLearn"
__credits__ = ["Brian Delhaisse"]
__license__ = "GNU GPLv3"
__version__ = "1.0.0"
__maintainer__ = "Brian Delhaisse"
__email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class SubscriberData(object):
r"""Subscriber data holder
"""
def __init__(self, topic, data_class):
self.subscriber = rospy.Subscriber(topic, data_class, callback=self.callback)
self.attributes = set([attr for attr in [attr for attr in dir(data_class) if not attr.startswith('_')]
if not callable(getattr(data_class, attr))])
self.subscriber_data = data_class()
def callback(self, data):
self.subscriber_data = data
def __getattr__(self, name):
return getattr(self.subscriber_data, name)
def unregister(self):
self.subscriber.unregister()
class Subscriber(object):
r"""Subscriber class
This Subscriber abstract class is the class from which all the other subscribers inherit from. It provides the
common functionalities between the various subscribers.
"""
def __init__(self, subscriber_id=None):
"""
Initialize the subscriber.
Args:
subscriber_id (int, None): subscriber id which is used when initializing the node. If None, a name will be
auto-generated for the name using name as the base. See the documentation for the :attr:`anonymous`
parameter in `rospy.init_node`.
"""
# initialize the node
if subscriber_id is None:
rospy.init_node(self.__class__.__name__, anonymous=True)
else:
rospy.init_node(self.__class__.__name__ + str(subscriber_id))
# all subscribers
self.subscribers = dict()
def create_subscriber(self, name, topic, data_class):
"""
Create a subscriber to the specific topic.
Args:
name (str): unique name of the subscriber. The name must be unique. You will be able to access to this
topic (str): name of the topic.
data_class (object): data type class to use for messages
Returns:
SubscriberData: the subscriber data holder.
"""
subscriber = SubscriberData(topic, data_class)
self.subscribers[name] = subscriber
return subscriber
def __getattr__(self, name):
return self.subscribers[name]
def unregister(self, name=None):
if name is None:
for subscriber in self.subscribers.values():
subscriber.unregister()
else:
self.subscribers[name].unregister()
def close(self):
self.unregister()
def __del__(self):
self.unregister()
class RobotSubscriber(Subscriber):
r"""Robot Subscriber class
This Robot Subscriber class is the class from which all the robot subscribers inherit from.
"""
def __init__(self, name, id_=None):
r"""
Initialize the robot subscriber.
Args:
name (str): name of the robot. This will be used to create the topics.
id_ (int, None): robot id which is used when initializing the node. If None, a name will be
auto-generated for the name using name as the base. See the documentation for the :attr:`anonymous`
parameter in `rospy.init_node`.
"""
super(RobotSubscriber, self).__init__(subscriber_id=id_)
self.name = name.lower()
# create Joint states
self.create_subscriber('joint_states', self.name + '/joint_states', sensor_msg.JointState)
def get_joint_positions(self, joint_ids):
if len(self.joint_states.position) >= len(joint_ids):
return np.asarray(self.joint_states.position) # [joint_ids]
return np.asarray(self.joint_states.position)
def get_joint_velocities(self, joint_ids):
if len(self.joint_states.velocity) >= len(joint_ids):
return np.asarray(self.joint_states.velocity)[joint_ids]
return np.asarray(self.joint_states.velocity)
def get_joint_torques(self, joint_ids):
if len(self.joint_states.effort) >= len(joint_ids):
return np.asarray(self.joint_states.effort)[joint_ids]
return np.asarray(self.joint_states.effort)
# Tests
if __name__ == '__main__':
# NOTE: run roscore before hand and don't forget to run the publisher code
import time
from itertools import count
subscriber = RobotSubscriber('walter')
print("Published topics: {}".format(rospy.get_published_topics()))
print("Robot joint state attributes: {}".format(subscriber.joint_states.attributes))
for t in count():
print(t)
print("Joint position data: {}".format(subscriber.joint_states.position))
time.sleep(0.1)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+167 -1
View File
@@ -18,6 +18,7 @@ References:
"""
from pyrobolearn.utils.data_structures.orderedset import OrderedSet
from pyrobolearn.simulators.middlewares.middleware import MiddleWare
__author__ = "Brian Delhaisse"
__copyright__ = "Copyright 2018, PyRoboLearn"
@@ -199,7 +200,7 @@ class Simulator(object):
self.real_time = False
self.kwargs = kwargs
self._num_instances = num_instances
self._middleware = middleware
self.middleware = middleware
# main camera in the simulator
self._camera = None
@@ -246,6 +247,19 @@ class Simulator(object):
"""Return the simulator time step."""
return self.get_time_step()
@property
def middleware(self):
"""Return the middleware."""
return self._middleware
@middleware.setter
def middleware(self, middleware):
"""Set the middleware."""
if middleware is not None and not isinstance(middleware, MiddleWare):
raise TypeError("Expecting the given 'middleware' to be an instance of `MiddleWare`, but got instead: "
"{}".format(middleware))
self._middleware = middleware
#############
# Operators #
#############
@@ -1695,6 +1709,26 @@ class Simulator(object):
"""
Set the position of the given joint(s) (using position control).
Args:
body_id (int): unique body id.
joint_ids (int, list[int]): joint id, or list of joint ids.
positions (float, np.array[float[N]]): desired position, or list of desired positions [rad]
velocities (None, float, np.array[float[N]]): desired velocity, or list of desired velocities [rad/s]
kps (None, float, np.array[float[N]]): position gain(s)
kds (None, float, np.array[float[N]]): velocity gain(s)
forces (None, float, np.array[float[N]]): maximum motor force(s)/torque(s) used to reach the target values.
"""
# set joint positions in the simulator
self._set_joint_positions(body_id, joint_ids, positions, velocities, kps, kds, forces)
# publish the joint positions through the middleware
if self.middleware is not None:
self.middleware.set_joint_positions(body_id, joint_ids, positions, velocities, kps, kds, forces)
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[int]): joint id, or list of joint ids.
@@ -1710,6 +1744,39 @@ class Simulator(object):
"""
Get the position of the given joint(s).
Args:
body_id (int): unique body id.
joint_ids (int, list[int]): joint id, or list of joint ids.
Returns:
if 1 joint:
float: joint position [rad]
if multiple joints:
np.array[float[N]]: joint positions [rad]
"""
# if a middleware is defined
if self.middleware is not None:
# get joint positions from the middleware
q = self.middleware.get_joint_positions(body_id, joint_ids)
if q is None: # if we didn't get the joint positions from the middleware, get them from the simulator
q = self._get_joint_positions(body_id, joint_ids)
else: # if we got them from the middleware, set them in the simulator
self._set_joint_positions(body_id=body_id, joint_ids=joint_ids, positions=q)
else:
# get the joint positions from the simulator
q = self._get_joint_positions(body_id, joint_ids)
# if the middleware is set on the teleoperation mode, publish the joint positions through the middleware
if self.middleware is not None:
self.middleware.set_joint_positions(body_id, joint_ids, q, check_teleoperate=True)
return q
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[int]): joint id, or list of joint ids.
@@ -1726,6 +1793,23 @@ class Simulator(object):
"""
Set the velocity of the given joint(s) (using velocity control).
Args:
body_id (int): unique body id.
joint_ids (int, list[int]): joint id, or list of joint ids.
velocities (float, np.array[float[N]]): desired velocity, or list of desired velocities [rad/s]
max_force (None, float, np.array[float[N]]): maximum motor forces/torques
"""
# set joint velocities in the simulator
self._set_joint_velocities(body_id, joint_ids, velocities, max_force)
# publish the joint velocities through the middleware
if self.middleware is not None:
self.middleware.set_joint_velocities(body_id, joint_ids, velocities, max_force)
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[int]): joint id, or list of joint ids.
@@ -1738,6 +1822,39 @@ class Simulator(object):
"""
Get the velocity of the given joint(s).
Args:
body_id (int): unique body id.
joint_ids (int, list[int]): joint id, or list of joint ids.
Returns:
if 1 joint:
float: joint velocity [rad/s]
if multiple joints:
np.array[float[N]]: joint velocities [rad/s]
"""
# if a middleware is defined
if self.middleware is not None:
# get joint velocities from the middleware
dq = self.middleware.get_joint_velocities(body_id, joint_ids)
if dq is None: # if we didn't get the joint velocities from the middleware, get them from the simulator
dq = self._get_joint_velocities(body_id, joint_ids)
else: # if we got them from the middleware, set them in the simulator
self._set_joint_velocities(body_id=body_id, joint_ids=joint_ids, velocities=dq)
else:
# get the joint velocities from the simulator
dq = self._get_joint_velocities(body_id, joint_ids)
# if the middleware is set on the teleoperation mode, publish the joint velocities through the middleware
if self.middleware is not None:
self.middleware.set_joint_velocities(body_id, joint_ids, dq, check_teleoperate=True)
return dq
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[int]): joint id, or list of joint ids.
@@ -1783,6 +1900,22 @@ class Simulator(object):
"""
Set the torque/force to the given joint(s) (using force/torque control).
Args:
body_id (int): unique body id.
joint_ids (int, list[int]): joint id, or list of joint ids.
torques (float, list[float], np.array[float]): desired torque(s) to apply to the joint(s) [N].
"""
# set joint torques in the simulator
self._set_joint_torques(body_id, joint_ids, torques)
# publish the joint torques through the middleware
if self.middleware is not None:
self.middleware.set_joint_torques(body_id, joint_ids, torques)
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[int]): joint id, or list of joint ids.
@@ -1794,6 +1927,39 @@ class Simulator(object):
"""
Get the applied torque(s) on the given joint(s).
Args:
body_id (int): unique body id.
joint_ids (int, list[int]): a joint id, or list of joint ids.
Returns:
if 1 joint:
float: torque [Nm]
if multiple joints:
np.array[float[N]]: torques associated to the given joints [Nm]
"""
# if a middleware is defined
if self.middleware is not None:
# get joint torques from the middleware
tau = self.middleware.get_joint_torques(body_id, joint_ids)
if tau is None: # if we didn't get the joint torques from the middleware, get them from the simulator
tau = self._get_joint_torques(body_id, joint_ids)
else: # if we got them from the middleware, set them in the simulator
self._set_joint_torques(body_id=body_id, joint_ids=joint_ids, torques=tau)
else:
# get the joint velocities from the simulator
tau = self._get_joint_torques(body_id, joint_ids)
# if the middleware is set on the teleoperation mode, publish the joint torques through the middleware
if self.middleware is not None:
self.middleware.set_joint_torques(body_id, joint_ids, tau, check_teleoperate=True)
return tau
def _get_joint_torques(self, body_id, joint_ids):
"""
Get the applied torque(s) on the given joint(s).
Args:
body_id (int): unique body id.
joint_ids (int, list[int]): a joint id, or list of joint ids.
+8 -3
View File
@@ -39,7 +39,7 @@ __email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
def convert_mesh(from_filename, to_filename, library='pyassimp'):
def convert_mesh(from_filename, to_filename, library='pyassimp', binary=False):
"""
Convert the given file containing the original mesh to the other specified format using the `pyassimp` library.
@@ -47,11 +47,16 @@ def convert_mesh(from_filename, to_filename, library='pyassimp'):
from_filename (str): filename of the mesh to convert.
to_filename (str): filename of the converted mesh.
library (str): library to use to convert the meshes. Select between 'pyassimp' and 'trimesh'.
binary (bool): if True, it will be in a binary format. This is only valid for some formats such as STL where
you have the ASCII version 'stl' and the binary version 'stlb'.
"""
if library == 'pyassimp':
scene = pyassimp.load(from_filename)
extension = to_filename.split('.')[-1]
pyassimp.export(scene, to_filename, file_type=extension)
extension = to_filename.split('.')[-1].lower()
if binary: # for binary add 'b' as a suffix. Ex: '<file>.stlb'
pyassimp.export(scene, to_filename, file_type=extension + 'b')
else:
pyassimp.export(scene, to_filename, file_type=extension)
pyassimp.release(scene)
elif library == 'trimesh':
export_mesh(trimesh.load(from_filename), to_filename)
@@ -190,7 +190,7 @@ class Frame(object):
using the right hand.
"""
# forward_axis=(1., 0., 0.), up_axis=(0., 0., 1.)):
self.name = None
self.position = position
self.orientation = orientation
self.dtype = dtype # world frame, body frame, joint frame, inertial frame, etc.
@@ -707,7 +707,7 @@ class Floor(object):
"""
self.name = name
self.dimensions = dimensions
self.frame = Frame(position, orientation, dtype='world')
self.frame = Frame(position=position, orientation=orientation, dtype='world')
if name is not None:
name = name + '_material'
self.material = Material(name=name, color=color, texture=texture)
@@ -867,7 +867,10 @@ class MultiBody(object):
self.joints = OrderedDict() # {name: Joint}
self.root = root
self.materials = {}
self.frame = Frame(position, orientation, dtype='world')
self.frame = Frame(position=position, orientation=orientation, dtype='world')
self.sensors = {}
self.actuators = {}
@property
def name(self):
@@ -1052,6 +1055,42 @@ class MultiBody(object):
# replace old joint dictionary
self.joints = joints
def has_sensors(self):
"""
Return True if the multi-body data structure has some sensors.
"""
return len(self.sensors) > 0
def has_actuators(self):
"""
Return True if the multi-body data structure has some actuators.
"""
return len(self.actuators) > 0
def add_sensor(self, sensor):
"""
Add a sensor to the multi-body data structure.
Args:
sensor (Sensor): sensor data structure.
"""
if not isinstance(sensor, Sensor):
raise TypeError("Expecting the given 'sensor' to be an instance of `Sensor`, but got instead: "
"{}".format(type(sensor)))
self.sensors[sensor.id] = sensor
def add_actuator(self, actuator):
"""
Add an actuator to the multi-body data structure.
Args:
actuator (Actuator): actuator data structure.
"""
if not isinstance(actuator, Actuator):
raise TypeError("Expecting the given 'actuator' to be an instance of `Actuator`, but got instead: "
"{}".format(type(actuator)))
self.actuators[actuator.id] = actuator
# alias
Tree = MultiBody
@@ -2831,20 +2870,39 @@ class Material(object):
self._emissive = self._check_color(emissive)
class Noise(object):
"""Noise distribution used in sensors and actuators."""
pass
class GaussianNoise(Noise):
"""Gaussian noise distribution used in sensors and actuators."""
def __init__(self, mean, stddev):
self.mean = mean
self.stddev = stddev
class Sensor(object):
r"""Sensor (abstract) class.
"""
def __init__(self, name=None, sensors=[]):
def __init__(self, sensor_id, name=None, update_rate=None, noise=None, sensors=[]):
"""
Initialize the sensor.
Args:
sensor_id (int): sensor unique id.
name (str): name of the sensor.
update_rate (float): update rate.
noise (Noise): noise that is applied on the sensor.
sensors (list[Sensor]): inner list of sensors.
"""
self.id = sensor_id
self.name = name
self.update_rate = update_rate
self.noise = noise
self.sensors = sensors
@property
@@ -2859,25 +2917,255 @@ class Sensor(object):
raise TypeError("Expecting the given 'name' to be a string, instead got: {}".format(type(name)))
self._name = name
@property
def noise(self):
"""Return the sensor noise."""
return self._noise
@noise.setter
def noise(self, noise):
"""Set the sensor noise."""
if noise is not None and not isinstance(noise, Noise):
raise TypeError("Expecting the given 'noise' to be an instance of `Noise` but got instead: "
"{}".format(type(noise)))
self._noise = noise
@property
def num_sensors(self):
"""Return the number of inner sensors."""
return len(self.sensors)
class Actuator(object): # Motor
r"""Actuator/Motor (abstract) class.
class JointSensor(Sensor):
"""Joint sensor
Sensor attached to a joint.
"""
def __init__(self, name=None, actuators=[]):
def __init__(self, sensor_id, joint, name=None, update_rate=None, noise=None):
"""
Initialize the joint sensor.
Args:
sensor_id (int): unique sensor id.
joint (Joint): joint to which the sensor is attached.
name (str): name of the sensor.
update_rate (float): update rate of the sensor.
noise (Noise): noise distribution that is used on the sensor.
"""
super(JointSensor, self).__init__(sensor_id, name, update_rate, noise)
self.joint = joint
@property
def joint(self):
"""Return the joint data structure."""
return self._joint
@joint.setter
def joint(self, joint):
"""Set the joint."""
if joint is not None and not isinstance(joint, Joint):
raise TypeError("Expecting the given 'joint' to be an instance of `Joint` but got instead: "
"{}".format(type(joint)))
self._joint = joint
class LinkSensor(Sensor):
"""Link Sensor
Sensor attached to a link.
"""
def __init__(self, sensor_id, link, name=None, update_rate=None, noise=None):
"""
Initialize the link sensor.
Args:
sensor_id (int): unique sensor id.
link (Body): link/body to which the sensor is attached.
name (str): name of the sensor.
update_rate (float): update rate of the sensor.
noise (Noise): noise distribution that is used on the sensor.
"""
super(LinkSensor, self).__init__(sensor_id, name, update_rate, noise)
self.link = link
@property
def link(self):
"""Return the link data structure."""
return self._link
@link.setter
def link(self, link):
"""Set the link."""
if link is not None and not isinstance(link, Body):
raise TypeError("Expecting the given 'link' to be an instance of `Body` but got instead: "
"{}".format(type(link)))
self._link = link
class CameraSensor(LinkSensor):
"""Camera sensor
References:
- http://gazebosim.org/tutorials?tut=ros_gzplugins#Camera
"""
def __init__(self, sensor_id, link, name=None, update_rate=None, noise=None):
"""
Initialize the camera sensor.
Args:
sensor_id (int): unique sensor id.
link (Body): link/body to which the sensor is attached.
name (str): name of the sensor.
update_rate (float): update rate of the sensor.
noise (Noise): noise distribution that is used on the sensor.
"""
super(CameraSensor, self).__init__(sensor_id, link, name, update_rate, noise)
self.frame = Frame()
self.visualize = False
# intrinsic properties of camera
self.horizontal_fov = None
self.width = None
self.height = None
self.format = None # R8G8B8
self.near = None
self.far = None
self.plugin_filename = None
self.plugin_name = None
self.camera_base_topic = None # camera_base_topic
self.image_topic = None # added to the camera_base_topic
self.camera_info_topic = None # added to the camera_base_topic
self.frame_name = None # check 'name' attribute in Frame class
self.hack_baseline = None
self.distortion_k1 = None
self.distortion_k2 = None
self.distortion_k3 = None
self.distortion_t1 = None
self.distortion_t2 = None
class DepthCameraSensor(LinkSensor):
"""Depth camera sensor
References:
- http://gazebosim.org/tutorials?tut=ros_gzplugins#Camera
"""
def __init__(self, sensor_id, link, name=None, update_rate=None, noise=None):
"""
Initialize the depth camera sensor.
Args:
sensor_id (int): unique sensor id.
link (Body): link/body to which the sensor is attached.
name (str): name of the sensor.
update_rate (float): update rate of the sensor.
noise (Noise): noise distribution that is used on the sensor.
"""
super(DepthCameraSensor, self).__init__(sensor_id, link, name, update_rate, noise)
class GPURay(LinkSensor):
"""GPU Ray sensor
References:
- http://gazebosim.org/tutorials?tut=ros_gzplugins#GPULaser
"""
def __init__(self, sensor_id, link, name=None, update_rate=None, noise=None):
"""
Initialize the GPU ray sensor.
Args:
sensor_id (int): unique sensor id.
link (Body): link/body to which the sensor is attached.
name (str): name of the sensor.
update_rate (float): update rate of the sensor.
noise (Noise): noise distribution that is used on the sensor.
"""
super(GPURay, self).__init__(sensor_id, link, name, update_rate, noise)
self.frame = Frame()
self.visualize = False
# <scan>
self.horizontal = None
self.samples = None
self.scan_resolution = None
self.range_angle = None # <min_angle> and <max_angle>
# <range>
self.range = None # <min> and <max>
self.range_resolution = 0.01
# plugin
self.plugin_filename = None
self.plugin_name = None
self.topic = None
self.frame_name = None # Check 'name' attribute in Frame class
class IMUSensor(LinkSensor):
"""IMU sensor.
References:
- http://gazebosim.org/tutorials?tut=ros_gzplugins#IMUsensor(GazeboRosImuSensor)
"""
def __init__(self, sensor_id, link, name=None, update_rate=None, noise=None):
"""
Initialize the IMU sensor.
Args:
sensor_id (int): unique sensor id.
link (Body): link/body to which the sensor is attached.
name (str): name of the sensor.
update_rate (float): update rate of the sensor.
noise (Noise): noise distribution that is used on the sensor.
"""
super(IMUSensor, self).__init__(sensor_id, link, name, update_rate, noise)
class ForceTorqueSensor(JointSensor):
"""Force torque sensor.
References:
- http://gazebosim.org/tutorials?tut=force_torque_sensor&cat=sensors
- http://docs.ros.org/jade/api/gazebo_plugins/html/group__GazeboRosFTSensor.html
"""
def __init__(self, sensor_id, joint, name=None, update_rate=None, noise=None):
"""
Initialize the F/T sensor.
Args:
sensor_id (int): unique sensor id.
joint (Joint): joint to which the sensor is attached.
name (str): name of the sensor.
update_rate (float): update rate of the sensor.
noise (Noise): noise distribution that is used on the sensor.
"""
super(ForceTorqueSensor, self).__init__(sensor_id, joint, name, update_rate, noise)
class Actuator(object): # Motor
r"""Actuator/Motor (abstract) class.
"""
def __init__(self, actuator_id, name=None, actuators=[]):
"""
Initialize the actuator/motor.
Args:
actuator_id (int): actuator unique id.
name (str): name of the actuator/motor.
actuators (list[Sensor]): inner list of actuators.
"""
self.id = actuator_id
self.name = name
self.actuators = actuators
@@ -2899,6 +3187,73 @@ class Actuator(object): # Motor
return len(self.actuators)
class JointActuator(Actuator):
"""Joint Actuator."""
def __init__(self, actuator_id, joint, name=None):
"""
Initialize the joint actuator.
Args:
actuator_id (int): unique actuator id.
joint (Joint): joint to which the actuator is attached.
name (str): name of the actuator.
"""
super(JointActuator, self).__init__(actuator_id, name)
self.joint = joint
@property
def joint(self):
"""Return the joint data structure."""
return self._joint
@joint.setter
def joint(self, joint):
"""Set the joint."""
if joint is not None and not isinstance(joint, Joint):
raise TypeError("Expecting the given 'joint' to be an instance of `Joint` but got instead: "
"{}".format(type(joint)))
self._joint = joint
class MotorJointActuator(JointActuator):
"""Motor joint actuator."""
def __init__(self, actuator_id, joint, name=None):
"""
Initialize the joint actuator.
Args:
actuator_id (int): unique actuator id.
joint (Joint): joint to which the actuator is attached.
name (str): name of the actuator.
"""
super(MotorJointActuator, self).__init__(actuator_id, joint, name)
self.transmission_type = None
self.hardware_interface = None # EffortJointInterface
self.mechanical_reduction = None
class PositionJointActuator(MotorJointActuator):
"""Position joint actuator."""
def __init__(self, actuator_id, joint, name=None):
"""
Initialize the position joint actuator.
Args:
actuator_id (int): unique actuator id.
joint (Joint): joint to which the actuator is attached.
name (str): name of the actuator.
"""
super(MotorJointActuator, self).__init__(actuator_id, joint, name)
self.p = None
self.i = None
self.d = None
class Heightmap(object):
r"""Heightmap (abstract) class.
@@ -2912,3 +3267,46 @@ class Constraint(object):
This allows to define a constraint.
"""
pass
class Transmission(object):
r"""Transmission interface.
The transmission interface is used in the control loop to "describe the relationship between an actuator and a
joint. This allows one to model concepts such as gear ratios and parallel linkages. A transmission transforms
efforts/flow variables such that their product - power - remains constant. Multiple actuators may be linked to
multiple joints through complex transmission." [3]
The control loop consists of 6 stages:
- read state from robotic hardware
- transmission: actuator to joint state
- controller manager update
Available transmission type:
- Simple reducer (type = transmission_interface/SimpleTransmission)
- Four-bar linkage
- Differential
References:
- [1] ROS Control: https://roscon.ros.org/2014/wp-content/uploads/2014/07/ros_control_an_overview.pdf
- [2] ros_control: http://wiki.ros.org/ros_control
- [3] URDF Transmissions: https://wiki.ros.org/urdf/XML/Transmission
"""
def __init__(self, name, joint, transmission_type=None, actuator_name=None, hardware_interface=None):
"""
Initialize the transmission.
Args:
name (str): name of the transmission.
joint (Joint): joint to which is attached the transmission.
transmission_type (str): transmission type; select between {'simple', 'four-bar linkage', 'differential'}.
actuator_name (str): name of the actuator.
hardware_interface (str): hardware interface.
"""
self.name = name
self.type = transmission_type
self.actuator = actuator_name
self.mechanical_reduction = None
self.joint = joint
self.hardware_interface = hardware_interface
@@ -1286,12 +1286,13 @@ class MuJoCoParser(WorldParser):
# if file does not already exists, convert it
if not os.path.isfile(new_filename):
# # Arf, pyassimp export an ASCII STL, but Mujoco requires a binary STL --> use trimesh
# scene = pyassimp.load(filename)
# pyassimp.export(scene, new_filename, file_type='stl')
# pyassimp.release(scene)
# use pyassimp to
scene = pyassimp.load(filename)
pyassimp.export(scene, new_filename, file_type='stlb')
pyassimp.release(scene)
export_mesh(trimesh.load(filename), new_filename)
#
# export_mesh(trimesh.load(filename), new_filename)
return new_filename
return filename
@@ -143,5 +143,6 @@ class RobotParser(object):
root (ET.Element): root element in the XML file.
"""
xml_str = self.get_string(root)
print(xml_str)
with open(filename, "w") as f:
f.write(xml_str) # .encode('utf-8'))
@@ -308,10 +308,13 @@ class URDFParser(RobotParser):
tree = self.tree
# create root element
root = ET.Element('robot')
attrib = {}
if tree.name is not None:
attrib['name'] = tree.name
root = ET.Element('robot', attrib=attrib)
# generate material tags
for material in tree.materials:
for material in tree.materials.values():
material_tag = ET.SubElement(root, 'material', attrib={'name': material.name})
if material.color is not None:
ET.SubElement(material_tag, 'color', attrib={'rgba': str(np.asarray(material.rgba))[1:-1]})
@@ -352,12 +355,15 @@ class URDFParser(RobotParser):
attrib['length'] = str(geometry.size[1])
else: # mesh
attrib['filename'] = geometry.filename
attrib['scale'] = str(np.asarray(geometry.size))[1:-1]
if geometry.size is not None:
size = geometry.size
size = np.array([size] * 3) if isinstance(size, (float, int)) else np.asarray(size)
attrib['scale'] = str(size)[1:-1]
ET.SubElement(geometry_tag, dtype, attrib=attrib)
# generate <link>
for link in tree.bodies:
for link in tree.bodies.values():
link_tag = ET.SubElement(root, 'link', attrib={'name': link.name})
# create <inertial> tag
@@ -434,7 +440,7 @@ class URDFParser(RobotParser):
return ET.SubElement(parent_tag, tag, attrib=kwargs)
# generate <joint>
for joint in tree.joints:
for joint in tree.joints.values():
# set joint name and type
joint_tag = set_name_and_type(root, 'joint', joint)
@@ -443,11 +449,11 @@ class URDFParser(RobotParser):
# <parent>
if joint.parent is not None:
ET.SubElement(joint_tag, 'parent', attrib={'link': joint.parent})
ET.SubElement(joint_tag, 'parent', attrib={'link': joint.parent.name})
# <child>
if joint.child is not None:
ET.SubElement(joint_tag, 'child', attrib={'link': joint.child})
ET.SubElement(joint_tag, 'child', attrib={'link': joint.child.name})
# <axis>
if joint.axis is not None: