10 #include "tools/cpp/runfiles/runfiles.h"
13 using bazel::tools::cpp::runfiles::Runfiles;
19 std::unique_ptr<Runfiles> runfiles(Runfiles::Create(argv0, &error));
20 if (runfiles ==
nullptr) {
21 throw std::runtime_error(
22 "Could not retrieve the package path to plane.urdf: " + error);
24 return runfiles->Rlocation(
"vulp/vulp/actuation/bullet/plane/plane.urdf");
31 auto flag = (params.
gui ? eCONNECT_GUI : eCONNECT_DIRECT);
32 bool is_connected = bullet_.connect(flag);
34 throw std::runtime_error(
"Could not connect to the Bullet GUI");
38 bullet_.configureDebugVisualizer(COV_ENABLE_GUI, 0);
39 bullet_.configureDebugVisualizer(COV_ENABLE_RENDERING, 0);
40 bullet_.configureDebugVisualizer(COV_ENABLE_SHADOWS, 0);
42 bullet_.setGravity(btVector3(0, 0, -9.81));
44 bullet_.setRealTimeSimulation(
false);
48 imu_link_index_ = get_link_index(
"imu");
49 if (imu_link_index_ < 0) {
50 throw std::runtime_error(
"Robot does not have a link named \"imu\"");
55 const auto& servo_id = id_joint.first;
56 const std::string& joint_name = id_joint.second;
57 moteus::ServoReply reply;
59 reply.result.temperature = 20.0;
60 reply.result.voltage = 18.0;
61 joint_index_map_.try_emplace(joint_name, -1);
62 servo_reply_.try_emplace(joint_name, reply);
66 b3JointInfo joint_info;
67 const int nb_joints = bullet_.getNumJoints(robot_);
68 for (
int joint_index = 0; joint_index < nb_joints; ++joint_index) {
69 bullet_.getJointInfo(robot_, joint_index, &joint_info);
70 std::string joint_name = joint_info.m_jointName;
71 if (joint_index_map_.find(joint_name) != joint_index_map_.end()) {
72 joint_index_map_[joint_name] = joint_index;
76 joint_properties_.try_emplace(joint_name, props);
83 throw std::runtime_error(
"Could not load the plane URDF!");
86 spdlog::info(
"Not loading the plane URDF");
89 "No ground plane was loaded, but gravity is enabled. The robot will "
96 spdlog::info(
"Loading environment URDF: ", urdf_path);
97 if (bullet_.loadURDF(urdf_path) < 0) {
98 throw std::runtime_error(
"Could not load the environment URDF: " +
104 bullet_.configureDebugVisualizer(COV_ENABLE_RENDERING, 1);
112 bullet_.setTimeStep(params_.
dt);
123 const Eigen::Vector3d& position_base_in_world,
124 const Eigen::Quaterniond& orientation_base_in_world,
125 const Eigen::Vector3d& linear_velocity_base_to_world_in_world,
126 const Eigen::Vector3d& angular_velocity_base_in_base) {
127 bullet_.resetBasePositionAndOrientation(
131 const auto& rotation_base_to_world =
132 orientation_base_in_world;
133 const Eigen::Vector3d angular_velocity_base_in_world =
135 bullet_.resetBaseVelocity(
147 const int nb_joints = bullet_.getNumJoints(robot_);
148 for (
int joint_index = 0; joint_index < nb_joints; ++joint_index) {
149 bullet_.resetJointState(robot_, joint_index, 0.0);
154 b3JointInfo joint_info;
155 const int nb_joints = bullet_.getNumJoints(robot_);
156 for (
int joint_index = 0; joint_index < nb_joints; ++joint_index) {
157 bullet_.getJointInfo(robot_, joint_index, &joint_info);
158 std::string joint_name = joint_info.m_jointName;
159 if (joint_index_map_.find(joint_name) != joint_index_map_.end()) {
162 joint_properties_[joint_name].friction = friction_it->second;
164 joint_properties_[joint_name].friction = 0.0;
174 observation(
"imu")(
"angular_velocity") =
176 observation(
"imu")(
"linear_acceleration") =
179 Dictionary& monitor = observation(
"bullet");
181 monitor(
"contact")(link_name)(
"num_contact_points") =
182 contact_data_.at(link_name).num_contact_points;
187 const moteus::Data& data,
188 std::function<
void(
const moteus::Output&)> callback) {
189 assert(
data.commands.size() ==
data.replies.size());
190 assert(!std::isnan(params_.
dt));
191 if (!bullet_.isConnected()) {
192 throw std::runtime_error(
"simulator is not running any more");
195 read_joint_sensors();
196 read_imu_data(imu_data_, bullet_, robot_, imu_link_index_, params_.
dt);
199 bullet_.stepSimulation();
202 translate_camera_to_robot();
205 moteus::Output output;
206 for (
size_t i = 0; i <
data.replies.size(); ++i) {
207 const auto servo_id =
data.commands[i].id;
209 data.replies[i].id = servo_id;
210 data.replies[i].result = servo_reply_[joint_name].result;
211 output.query_result_size = i + 1;
216 void BulletInterface::read_contacts() {
217 b3ContactInformation contact_info;
218 b3RobotSimulatorGetContactPointsArgs contact_args;
220 contact_args.m_bodyUniqueIdA = robot_;
221 contact_args.m_linkIndexA = get_link_index(link_name);
222 bullet_.getContactPoints(contact_args, &contact_info);
223 contact_data_[link_name].num_contact_points =
224 contact_info.m_numContactPoints;
228 void BulletInterface::read_joint_sensors() {
229 b3JointSensorState sensor_state;
230 for (
const auto& name_index : joint_index_map_) {
231 const auto& joint_name = name_index.first;
232 const auto joint_index = name_index.second;
233 bullet_.getJointState(robot_, joint_index, &sensor_state);
234 auto& result = servo_reply_[joint_name].result;
235 result.position = sensor_state.m_jointPosition / (2.0 * M_PI);
236 result.velocity = sensor_state.m_jointVelocity / (2.0 * M_PI);
237 result.torque = sensor_state.m_jointMotorTorque;
241 void BulletInterface::send_commands(
const moteus::Data& data) {
242 b3RobotSimulatorJointMotorArgs motor_args(CONTROL_MODE_VELOCITY);
244 for (
const auto& command :
data.commands) {
245 const auto servo_id = command.id;
247 const int joint_index = joint_index_map_[joint_name];
249 const auto previous_mode = servo_reply_[joint_name].result.mode;
250 if (previous_mode == moteus::Mode::kStopped &&
251 command.mode != moteus::Mode::kStopped) {
253 motor_args.m_controlMode = CONTROL_MODE_VELOCITY;
254 motor_args.m_maxTorqueValue = 0.;
255 bullet_.setJointMotorControl(robot_, joint_index, motor_args);
257 servo_reply_[joint_name].result.mode = command.mode;
259 if (command.mode == moteus::Mode::kStopped) {
260 motor_args.m_controlMode = CONTROL_MODE_VELOCITY;
261 motor_args.m_maxTorqueValue = 100.;
262 motor_args.m_targetVelocity = 0.;
263 bullet_.setJointMotorControl(robot_, joint_index, motor_args);
267 if (command.mode != moteus::Mode::kPosition) {
268 throw std::runtime_error(
269 "Bullet interface does not support command mode " +
270 std::to_string(
static_cast<unsigned>(command.mode)));
273 const double target_position = command.position.position * (2.0 * M_PI);
274 const double target_velocity = command.position.velocity * (2.0 * M_PI);
275 const double feedforward_torque = command.position.feedforward_torque;
276 const double kp_scale = command.position.kp_scale;
277 const double kd_scale = command.position.kd_scale;
278 const double maximum_torque = command.position.maximum_torque;
280 joint_name, feedforward_torque, target_position, target_velocity,
281 kp_scale, kd_scale, maximum_torque);
282 motor_args.m_controlMode = CONTROL_MODE_TORQUE;
283 motor_args.m_maxTorqueValue = joint_torque;
284 servo_reply_[joint_name].result.torque = joint_torque;
285 bullet_.setJointMotorControl(robot_, joint_index, motor_args);
290 const std::string& joint_name,
const double feedforward_torque,
291 const double target_position,
const double target_velocity,
292 const double kp_scale,
const double kd_scale,
const double maximum_torque) {
293 assert(!std::isnan(target_velocity));
295 const auto& measurements = servo_reply_[joint_name].result;
296 const double measured_position = measurements.position * (2.0 * M_PI);
297 const double measured_velocity = measurements.velocity * (2.0 * M_PI);
300 const double tau_max = std::min(maximum_torque, joint_props.
maximum_torque);
301 double torque = feedforward_torque;
302 torque += kd * (target_velocity - measured_velocity);
303 if (!std::isnan(target_position)) {
304 torque += kp * (target_position - measured_position);
306 constexpr
double kMaxStictionVelocity = 1e-3;
307 if (std::abs(measured_velocity) > kMaxStictionVelocity) {
308 torque += joint_props.
friction * ((measured_velocity > 0.0) ? -1.0 : +1.0);
310 torque = std::max(std::min(torque, tau_max), -tau_max);
315 btVector3 position_base_in_world;
316 btQuaternion orientation_base_in_world;
317 bullet_.getBasePositionAndOrientation(robot_, position_base_in_world,
318 orientation_base_in_world);
320 Eigen::Matrix4d T = Eigen::Matrix4d::Identity();
321 T.block<3, 3>(0, 0) = quat.normalized().toRotationMatrix();
336 btVector3 angular_velocity_base_to_world_in_world;
338 bullet_.getBaseVelocity(robot_, _, angular_velocity_base_to_world_in_world);
340 Eigen::Matrix3d rotation_base_to_world = T.block<3, 3>(0, 0);
341 return rotation_base_to_world.transpose() *
345 void BulletInterface::translate_camera_to_robot() {
346 b3OpenGLVisualizerCameraInfo camera_info;
347 btVector3 position_base_in_world;
348 btQuaternion orientation_base_in_world;
349 bullet_.getBasePositionAndOrientation(robot_, position_base_in_world,
350 orientation_base_in_world);
351 bullet_.getDebugVisualizerCamera(&camera_info);
352 bullet_.resetDebugVisualizerCamera(camera_info.m_dist, camera_info.m_pitch,
353 camera_info.m_yaw, position_base_in_world);
356 int BulletInterface::get_link_index(
const std::string& link_name) {
357 if (link_index_.find(link_name) != link_index_.end()) {
358 return link_index_[link_name];
361 link_index_[link_name] = link_index;
void cycle(const moteus::Data &data, std::function< void(const moteus::Output &)> callback) final
Spin a new communication cycle.
void reset(const Dictionary &config) override
Reset interface.
Eigen::Vector3d angular_velocity_base_in_base() const noexcept
Get the groundtruth floating base angular velocity.
void reset_joint_angles()
Reset joint angles to zero.
void reset_joint_properties()
Reset joint properties to defaults.
~BulletInterface()
Disconnect interface.
void reset_contact_data()
Reset contact data.
double compute_joint_torque(const std::string &joint_name, const double feedforward_torque, const double target_position, const double target_velocity, const double kp_scale, const double kd_scale, const double maximum_torque)
Reproduce the moteus position controller in Bullet.
void observe(Dictionary &observation) const override
Write actuation-interface observations to dictionary.
Eigen::Matrix4d transform_base_to_world() const noexcept
Get the groundtruth floating base transform.
Eigen::Vector3d linear_velocity_base_to_world_in_world() const noexcept
Get the groundtruth floating base linear velocity.
BulletInterface(const ServoLayout &layout, const Parameters ¶ms)
Initialize interface.
void reset_base_state(const Eigen::Vector3d &position_base_in_world, const Eigen::Quaterniond &orientation_base_in_world, const Eigen::Vector3d &linear_velocity_base_to_world_in_world, const Eigen::Vector3d &angular_velocity_base_in_base)
Reset the pose and velocity of the floating base in the world frame.
Base class for actuation interfaces.
moteus::Data & data()
Get joint command-reply data.
const ServoLayout & servo_layout() const noexcept
Get servo layout.
const std::map< int, std::string > & servo_joint_map() const noexcept
Map from servo ID to joint name.
Map between servos, their busses and the joints they actuate.
const std::string & joint_name(const int servo_id) const
Get the name of the joint a servo actuates.
Send actions to actuators or simulators.
void read_imu_data(BulletImuData &imu_data, b3RobotSimulatorClientAPI &bullet, int robot, const int imu_link_index, double dt)
Compute IMU readings from the IMU link state.
Eigen::Quaterniond eigen_from_bullet(const btQuaternion &quat)
Convert a Bullet quaternion to an Eigen one.
std::string find_plane_urdf(const std::string argv0)
btQuaternion bullet_from_eigen(const Eigen::Quaterniond &quat)
Convert an Eigen quaternion to a Bullet one.
int find_link_index(b3RobotSimulatorClientAPI &bullet, int robot, const std::string &link_name) noexcept
Find the index of a link.
std::string robot_urdf_path
Path to the URDF model of the robot.
bool gravity
If true, set gravity to -9.81 m/s².
double dt
Simulation timestep in [s].
bool gui
If true, fire up the graphical user interface.
double torque_control_kd
Gain for joint velocity control feedback.
bool floor
If true, load a floor plane.
std::map< std::string, double > joint_friction
Joint friction parameters.
std::vector< std::string > monitor_contacts
Contacts to monitor and report along with observations.
Eigen::Vector3d linear_velocity_base_to_world_in_world
Linear velocity of the base in the world frame upon reset.
Eigen::Quaterniond orientation_base_in_world
Orientation of the base in the world frame upon reset.
Eigen::Vector3d angular_velocity_base_in_base
Body angular velocity of the base upon reset.
Eigen::Vector3d position_base_in_world
Position of the base in the world frame upon reset.
std::vector< std::string > env_urdf_paths
Paths to environment URDFs to load.
std::string argv0
Value of argv[0] used to locate runfiles (e.g.
void configure(const Dictionary &config)
Configure from dictionary.
double torque_control_kp
Gain for joint position control feedback.
bool follower_camera
Translate the camera to follow the robot.
Properties for robot joints in the Bullet simulation.
double maximum_torque
Maximum torque, in N.m.
double friction
Kinetic friction, in N.m.
Eigen::Quaterniond orientation_imu_in_ars
Orientation from the IMU frame to the attitude reference system (ARS) frame.
Eigen::Vector3d linear_acceleration_imu_in_imu
Body linear acceleration of the IMU, in [m] / [s]².
Eigen::Vector3d angular_velocity_imu_in_imu
Body angular velocity of the IMU frame in [rad] / [s].