Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
50048eb
Add rough version of DeleteEntity
azeey Jul 22, 2025
7c6b474
Cleanup
azeey Jul 25, 2025
d5829f4
Add initial implementation for GetEntityState
azeey Jul 25, 2025
965859a
Add initial implementation for GetEntitiesStates
azeey Jul 25, 2025
fbc34a0
Add initial implementation for GetSimulationState
azeey Jul 25, 2025
7898841
Refactor
azeey Jul 25, 2025
05eaac0
Add GetSimulatorFeatures
azeey Jul 25, 2025
c418a2c
Add ResetSimulation
azeey Jul 25, 2025
054b56c
Add SetSimulationState
azeey Jul 25, 2025
0c0fe85
Add SpawnEntity
azeey Jul 25, 2025
f02fe82
Add StepSimulation
azeey Jul 25, 2025
7e29730
Add SimulateSteps action
azeey Jul 25, 2025
310e426
Large refactor
azeey Jul 29, 2025
f147f1f
Update CI to use nightlies from Jetty
azeey Jul 29, 2025
9852cf8
Fix typo
azeey Jul 29, 2025
06ffa9b
Include stable repos
azeey Jul 29, 2025
50ca3c5
Rename GazeboState to GazeboProxy
azeey Jul 31, 2025
5328944
Add GetEntityInfo
azeey Aug 1, 2025
a7ec767
Refactor get_entity_state
azeey Aug 1, 2025
305faf0
Simplify WithLocked
azeey Aug 1, 2025
5079fb3
Support EntityFilter
azeey Aug 1, 2025
7e44347
Add SetEntityState, but only for pose
azeey Aug 6, 2025
b6257f5
Support velocities for SetEntityState
azeey Aug 7, 2025
6c4724f
Add gazebo_proxy.cpp
azeey Aug 7, 2025
f743316
Run clang-format
azeey Aug 7, 2025
df10293
Support EntityFilters in GetEntitiesStates
azeey Aug 8, 2025
4f482db
Support getting velocities in GetEntityState
azeey Aug 13, 2025
b584150
Set velocities only on non-static models
azeey Aug 13, 2025
7d1918a
Update to simulation_interfaces 2.0
azeey Aug 14, 2025
9ae3723
Set header on state messages
azeey Aug 15, 2025
6583fb8
Add preliminary test
azeey Aug 15, 2025
64f719f
First meaningful test
azeey Aug 15, 2025
783b26a
Revert to using unique lock, fix race condition
azeey Aug 17, 2025
060f6f9
Add wait for updated state
azeey Aug 17, 2025
3387567
Handle newly created entities
azeey Aug 17, 2025
01ca3c2
Fix linter issues
azeey Aug 17, 2025
5df66ab
Handle newly created entities
azeey Aug 18, 2025
a5d7024
Use Gazebo's signal handlers
azeey Aug 18, 2025
8f9d5c5
Wait for services and topics
azeey Aug 19, 2025
bb79567
Fix linter issues
azeey Aug 19, 2025
9ee5bdf
Use sub-namespace for simulation_interfaces
azeey Aug 19, 2025
d258f7e
Depend on gz-sim to fix build order
azeey Aug 19, 2025
a29eeb6
Merge remote-tracking branch 'origin/ros2' into standard_interfaces
azeey Aug 19, 2025
dd990d5
Add API docs
azeey Aug 19, 2025
18064d0
Split out gz_entity_filter implementation into a cpp file
azeey Aug 19, 2025
74d7491
Add more API docs
azeey Aug 19, 2025
d18410f
Fix uncrustify
azeey Aug 19, 2025
0ff3086
Remove `simulation_interfaces` prefix per feedback
azeey Aug 22, 2025
e4db83e
Added tests covering all interfaces
sauk2 Aug 24, 2025
ccb16ee
Updated the test cases with comments
sauk2 Aug 24, 2025
d6bdc85
Style changes
sauk2 Aug 24, 2025
1de6c0d
Merge branch 'ros2' into standard_interfaces
ahcorde Aug 25, 2025
4434df4
Merge pull request #1 from sauk2/standard_interfaces
azeey Aug 25, 2025
19b2e34
Remove packages.osrfoundation.org as we only need to depend on vendor…
azeey Sep 8, 2025
72ba4a4
Build Jetty vendor packages from source
azeey Sep 8, 2025
acced3b
Add git
azeey Sep 8, 2025
3398a4b
Restore all commented out gz_vendor statements
azeey Sep 8, 2025
09d5c23
Add missing headers
azeey Sep 9, 2025
a649e5d
Address reviewr feedback
azeey Sep 9, 2025
821993e
fix uncrustify errors
azeey Sep 9, 2025
391fe45
Fix linter
azeey Sep 9, 2025
b3e37d4
Address reviewer feedback
azeey Sep 9, 2025
53a6ae8
Return an error code in GetEntitiesStates
azeey Sep 9, 2025
566f7c9
Don't build dartsim and ogre-next
azeey Sep 9, 2025
9c57205
Add missing headers, fix indentation, alphabetize
azeey Sep 9, 2025
237436c
Revert changes in build-and-test.sh now that Jetty vendor packages ha…
azeey Sep 10, 2025
6bcbc8a
Fix cpplint issues
azeey Sep 10, 2025
e855839
More uncrustify fixes
azeey Sep 10, 2025
a494331
Fix typos
azeey Sep 11, 2025
c49de80
Try to make the test less flaky
azeey Sep 11, 2025
6e05a1a
Fix flake8
azeey Sep 11, 2025
15e81b7
Reinitialize canonical links after reset
azeey Sep 26, 2025
2d3b276
Test set_entity_state
azeey Sep 26, 2025
1c6c699
Fix action namespace, print actual service/action name
azeey Sep 26, 2025
d8bbb15
Fix duplicate node warning, set GZ_IP once, check result in more places
azeey Sep 26, 2025
d8721f1
Fix crash at shutdown
azeey Sep 26, 2025
dfa43a0
Fix style
azeey Sep 26, 2025
3f37e64
Make reset and setting simulation state more reliable
azeey Sep 26, 2025
e0d970a
Handle existing goal, fix crash related to invalid goal during destru…
azeey Sep 26, 2025
b7f421b
Use CreateSubscriber to ensure that topics are unsubscribed during de…
azeey Sep 27, 2025
a2458fa
Fix style
azeey Sep 27, 2025
7dc79c6
Fix issue with running the SimulateSteps action repeatedly, add/fix test
azeey Sep 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ros_gz_sim/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ find_package(gz-sim REQUIRED)
find_package(gz_transport_vendor REQUIRED)
find_package(gz-transport REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_action REQUIRED)
find_package(rclcpp_components REQUIRED)
find_package(rcpputils REQUIRED)
find_package(ros_gz_interfaces REQUIRED)
Expand Down Expand Up @@ -83,6 +84,7 @@ target_link_libraries(gzserver_component PUBLIC
${simulation_interfaces_TARGETS}
gz-sim::core
rclcpp::rclcpp
rclcpp_action::rclcpp_action
rclcpp_components::component
rclcpp_components::component_manager
)
Expand Down
1 change: 1 addition & 0 deletions ros_gz_sim/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<depend>launch_ros</depend>
<depend>libgflags-dev</depend>
<depend>rclcpp</depend>
<depend>rclcpp_action</depend>
<depend>rclcpp_components</depend>
<depend>rcpputils</depend>
<depend>ros_gz_interfaces</depend>
Expand Down
231 changes: 223 additions & 8 deletions ros_gz_sim/src/simulation_interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <gz/msgs/world_control.pb.h>
#include <gz/msgs/world_stats.pb.h>

#include <cstdint>
#include <functional>
#include <gz/math/Pose3.hh>
#include <gz/sim/EntityComponentManager.hh>
Expand All @@ -30,9 +31,16 @@
#include <gz/sim/components/Name.hh>
#include <gz/transport/Node.hh>
#include <iostream>
#include <limits>
#include <memory>
#include <mutex>
#include <rclcpp/rclcpp.hpp>
#include <rclcpp/utilities.hpp>
#include <rclcpp_action/rclcpp_action.hpp>
#include <rclcpp_action/server.hpp>

#include "simulation_interfaces/action//simulate_steps.hpp"
#include "simulation_interfaces/action/simulate_steps.hpp"
#include "simulation_interfaces/msg/result.hpp"
#include "simulation_interfaces/msg/simulation_state.hpp"
#include "simulation_interfaces/msg/simulator_features.hpp"
Expand All @@ -45,6 +53,7 @@
#include "simulation_interfaces/srv/reset_simulation.hpp"
#include "simulation_interfaces/srv/set_simulation_state.hpp"
#include "simulation_interfaces/srv/spawn_entity.hpp"
#include "simulation_interfaces/srv/step_simulation.hpp"

namespace components = gz::sim::components;

Expand All @@ -54,6 +63,7 @@ namespace ros_gz_sim
class SimulationInterfaces::Implementation
{
public:
// Services
using DeleteEntity = simulation_interfaces::srv::DeleteEntity;
using GetEntities = simulation_interfaces::srv::GetEntities;
using GetEntityState = simulation_interfaces::srv::GetEntityState;
Expand All @@ -63,13 +73,25 @@ class SimulationInterfaces::Implementation
using ResetSimulation = simulation_interfaces::srv::ResetSimulation;
using SetSimulationState = simulation_interfaces::srv::SetSimulationState;
using SpawnEntity = simulation_interfaces::srv::SpawnEntity;
using StepSimulation = simulation_interfaces::srv::StepSimulation;

// Actions
using SimulateSteps = simulation_interfaces::action::SimulateSteps;
using GoalHandleSimulateSteps = rclcpp_action::ServerGoalHandle<SimulateSteps>;

void Run(rclcpp::Node & node);
std::string PrefixTopic(const char * topic);
void UpdateStateFromMsg(const gz::msgs::SerializedStepMap & msg);
void CreateServices(rclcpp::Node & node);
template <typename Service, typename HandlerFunc>
void AddService(rclcpp::Node & node, const char * service_name, HandlerFunc && callback);
template <
typename Action, typename GoalHandlerFunc, typename CancelHandlerFunc,
typename AcceptedHandlerFunc>
void AddAction(
rclcpp::Node & node, const char * service_name, GoalHandlerFunc && goal_callback,
CancelHandlerFunc && cancel_handler, AcceptedHandlerFunc && accepted_handler);

bool InitializeGazeboParameters();

// Service handlers
Expand All @@ -96,13 +118,23 @@ class SimulationInterfaces::Implementation
SetSimulationState::Response::SharedPtr response);
void SpawnEntityCb(
SpawnEntity::Request::ConstSharedPtr request, SpawnEntity::Response::SharedPtr response);
void StepSimulationCb(
StepSimulation::Request::ConstSharedPtr request, StepSimulation::Response::SharedPtr response);

// Action handlers
rclcpp_action::GoalResponse SimulateStepsGoalCb(
const rclcpp_action::GoalUUID & uuid, std::shared_ptr<const SimulateSteps::Goal> goal);
rclcpp_action::CancelResponse SimulateStepsCancelCb(
const std::shared_ptr<GoalHandleSimulateSteps> goal_handle);
void SimulateStepsAcceptedCb(const std::shared_ptr<GoalHandleSimulateSteps> goal_handle);

// Service helpers
bool PopulateStateFromEcm(
const gz::sim::Entity & entity, simulation_interfaces::msg::EntityState & state,
simulation_interfaces::msg::Result & result);

private:
// TODO(azeey) Consider storing the ROS node
gz::transport::Node gz_node_;
const unsigned int kTimeout_{5000};
std::string world_name_;
Expand All @@ -111,6 +143,7 @@ class SimulationInterfaces::Implementation
gz::msgs::WorldStatistics world_stats_;

std::vector<std::shared_ptr<rclcpp::ServiceBase>> services_handles_;
std::vector<std::shared_ptr<rclcpp_action::ServerBase>> action_handles_;
};

void SimulationInterfaces::Implementation::Run(rclcpp::Node & node)
Expand All @@ -122,8 +155,9 @@ void SimulationInterfaces::Implementation::Run(rclcpp::Node & node)
}

// TODO(azeey) Consider adding the UserCommands system if not already present.
// TODO(azeey) Wait for critical services to be available (e.g. /world/*/create, /world/*/control)
// Request the initial state of the world. This will block until Gazebo is initialized
// TODO(azeey) Wait for critical services to be available (e.g. /world/*/create,
// /world/*/control) Request the initial state of the world. This will block until Gazebo is
// initialized
gz::msgs::SerializedStepMap reply;
bool result;
if (!this->gz_node_.Request(this->PrefixTopic("state"), 30000, reply, result)) {
Expand Down Expand Up @@ -171,7 +205,8 @@ void SimulationInterfaces::Implementation::UpdateStateFromMsg(
this->ecm_.ProcessRemoveEntityRequests();
this->world_stats_ = msg.stats();

// TODO(azeey) Consider using a condition variable to notify services that there is new data so as to avoid using stale data
// TODO(azeey) Consider using a condition variable to notify services that there is new data so as
// to avoid using stale data
}

void SimulationInterfaces::Implementation::CreateServices(rclcpp::Node & node)
Expand All @@ -190,6 +225,11 @@ void SimulationInterfaces::Implementation::CreateServices(rclcpp::Node & node)
this->AddService<SetSimulationState>(
node, "set_simulation_state", &Implementation::SetSimulationStateCb);
this->AddService<SpawnEntity>(node, "spawn_entity", &Implementation::SpawnEntityCb);
this->AddService<StepSimulation>(node, "step_simulation", &Implementation::StepSimulationCb);

this->AddAction<SimulateSteps>(
node, "simulate_steps", &Implementation::SimulateStepsGoalCb,
&Implementation::SimulateStepsCancelCb, &Implementation::SimulateStepsAcceptedCb);
}

template <typename Service, typename HandlerFunc>
Expand All @@ -202,6 +242,23 @@ void SimulationInterfaces::Implementation::AddService(
RCLCPP_INFO_STREAM(node.get_logger(), "Created service " << service_name);
}

template <
typename Action, typename GoalHandlerFunc, typename CancelHandlerFunc,
typename AcceptedHandlerFunc>
void SimulationInterfaces::Implementation::AddAction(
rclcpp::Node & node, const char * action_name, GoalHandlerFunc && goal_callback,
CancelHandlerFunc && cancel_callback, AcceptedHandlerFunc && accepted_callback)
{
this->action_handles_.push_back(
rclcpp_action::create_server<Action>(
&node, action_name,
std::bind(goal_callback, this, std::placeholders::_1, std::placeholders::_2),
std::bind(cancel_callback, this, std::placeholders::_1),
std::bind(accepted_callback, this, std::placeholders::_1)));

RCLCPP_INFO_STREAM(node.get_logger(), "Created action " << action_name);
}

bool SimulationInterfaces::Implementation::InitializeGazeboParameters()
{
gz::msgs::StringMsg_V worlds_msg;
Expand Down Expand Up @@ -231,7 +288,8 @@ void SimulationInterfaces::Implementation::DeleteEntityCb(
return;
}
}
// TODO(azeey) Add specific error codes depending on what went wrong and add more thorough error messages.
// TODO(azeey) Add specific error codes depending on what went wrong and add more thorough error
// messages.
response->result.result = simulation_interfaces::msg::Result::RESULT_OPERATION_FAILED;
response->result.error_message = "Error while trying to remove entity";
}
Expand Down Expand Up @@ -259,7 +317,8 @@ void SimulationInterfaces::Implementation::GetEntityStateCb(
GetEntityState::Request::ConstSharedPtr request, GetEntityState::Response::SharedPtr response)
{
std::lock_guard<std::mutex> lk(this->stateSyncMutex_);
// TODO (azeey) Since the name might not be unique across Gazebo entity types, ensure that the matched entity is a model.
// TODO (azeey) Since the name might not be unique across Gazebo entity types, ensure that the
// matched entity is a model.
auto entity = this->ecm_.EntityByName(request->entity);
if (entity) {
this->PopulateStateFromEcm(*entity, response->state, response->result);
Expand Down Expand Up @@ -312,7 +371,8 @@ void SimulationInterfaces::Implementation::GetSimulationStateCb(
if (this->world_stats_.paused()) {
response->state.state = simulation_interfaces::msg::SimulationState::STATE_PAUSED;
if (this->world_stats_.iterations() == 0) {
// The simulation is in its initial state after loading a world or being reset, which will assign to the STATE_STOPPED state
// The simulation is in its initial state after loading a world or being reset, which will
// assign to the STATE_STOPPED state
response->state.state = simulation_interfaces::msg::SimulationState::STATE_STOPPED;
}
} else {
Expand Down Expand Up @@ -477,12 +537,167 @@ void SimulationInterfaces::Implementation::SpawnEntityCb(
// We'd have to make sure that the ECM has been updated at least once after the `create` request
response->entity_name = request->name;
} else {
// TODO(azeey) SpawnEntity has additional error codes to allow surfacing more informative error messages.
// However, the `create` service in UserCommands only returns a boolean.
// TODO(azeey) SpawnEntity has additional error codes to allow surfacing more informative error
// messages. However, the `create` service in UserCommands only returns a boolean.
response->result.result = Result::RESULT_OPERATION_FAILED;
response->result.error_message = "Unknown error while tryint to reset simulation";
}
}

void SimulationInterfaces::Implementation::StepSimulationCb(
StepSimulation::Request::ConstSharedPtr request, StepSimulation::Response::SharedPtr response)
{
using Result = simulation_interfaces::msg::Result;
bool sim_paused;
{
std::lock_guard<std::mutex> lk(this->stateSyncMutex_);
sim_paused = this->world_stats_.paused();
}
if (!sim_paused) {
response->result.result = Result::RESULT_OPERATION_FAILED;
response->result.error_message = "Simulation has to be paused before stepping";
return;
}

// The spec uses a uint64, but the service provided by Gazebo uses a uint32 so we bail out if the
// requested number of steps cannot be represented properly.
if (request->steps > std::numeric_limits<uint32_t>::max()) {
response->result.result = Result::RESULT_OPERATION_FAILED;
response->result.error_message =
"The requested number of steps exceeds the maximum supported value (max uint32)";
return;
}

gz::msgs::WorldControl gz_request;
gz_request.set_pause(true);
gz_request.set_step(true);
gz_request.set_multi_step(request->steps);
bool result;
gz::msgs::Boolean reply;
bool executed =
this->gz_node_.Request(this->PrefixTopic("control"), gz_request, 30000, reply, result);
if (!executed) {
response->result.result = Result::RESULT_OPERATION_FAILED;
response->result.error_message = "Timed out while trying to reset simulation";
} else if (result && reply.data()) {
response->result.result = Result::RESULT_OK;
} else {
response->result.result = Result::RESULT_OPERATION_FAILED;
response->result.error_message = "Unknown error while trying to reset simulation";
}
}

rclcpp_action::GoalResponse SimulationInterfaces::Implementation::SimulateStepsGoalCb(
const rclcpp_action::GoalUUID &, std::shared_ptr<const SimulateSteps::Goal>)
{
// TODO(azeey) Add console message that we've received the goal
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
}

rclcpp_action::CancelResponse SimulationInterfaces::Implementation::SimulateStepsCancelCb(
const std::shared_ptr<GoalHandleSimulateSteps>)
{
// TODO(azeey) Add console message that we've received the cancellation
return rclcpp_action::CancelResponse::ACCEPT;
}

void SimulationInterfaces::Implementation::SimulateStepsAcceptedCb(
const std::shared_ptr<GoalHandleSimulateSteps> goal_handle)
{
// Note that this is not the same as SimulateSteps::Result, which is the typename associated with
// the Result of the action, which in turn contains a simulation_interfaces::msg::Result
using Result = simulation_interfaces::msg::Result;

// Execute the task in a separate thread and return immediately.
auto thread = std::thread([this, goal_handle] {
gz::transport::Node action_gz_node;
const auto goal = goal_handle->get_goal();
auto action_result = std::make_shared<SimulateSteps::Result>();

if (goal->steps > std::numeric_limits<uint32_t>::max()) {
action_result->result.result = simulation_interfaces::msg::Result::RESULT_OPERATION_FAILED;
action_result->result.error_message =
"The requested number of steps exceeds the maximum supported value (max uint32)";
goal_handle->abort(action_result);
return;
}

bool sim_paused;
{
std::lock_guard<std::mutex> lk(this->stateSyncMutex_);
sim_paused = this->world_stats_.paused();
}
if (!sim_paused) {
action_result->result.result = simulation_interfaces::msg::Result::RESULT_OPERATION_FAILED;
action_result->result.error_message = "Simulation has to be paused before stepping";
goal_handle->abort(action_result);
return;
}

uint64_t num_iters_start = 0;
{
std::lock_guard<std::mutex> lk(this->stateSyncMutex_);
num_iters_start = this->world_stats_.iterations();
}

// TODO(azeey) Refactor this code since it's also used in the StepSimulation service.
gz::msgs::WorldControl gz_request;
gz_request.set_pause(true);
gz_request.set_step(true);
gz_request.set_multi_step(goal->steps);
bool gz_result;
gz::msgs::Boolean reply;
bool executed =
action_gz_node.Request(this->PrefixTopic("control"), gz_request, 30000, reply, gz_result);
if (!executed) {
action_result->result.result = Result::RESULT_OPERATION_FAILED;
action_result->result.error_message = "Timed out while trying to reset simulation";
goal_handle->abort(action_result);
} else if (action_result && reply.data()) {
// The request has succeeded, so now we listen to the stats and provide feedback.
action_result->result.result = Result::RESULT_OK;
auto feedback = std::make_shared<SimulateSteps::Feedback>();

while (rclcpp::ok()) {
std::lock_guard<std::mutex> lk(this->stateSyncMutex_);
auto iterations = this->world_stats_.iterations();
feedback->completed_steps = iterations - num_iters_start;
feedback->remaining_steps = goal->steps - feedback->completed_steps;
goal_handle->publish_feedback(feedback);
// TODO(azeey) There is a bug in Gazebo where the stepping field is set to true only once
// immediately after the request to step instead of being true for the whole duration of
// steps. So we can't use this right now to determine if we need to break out early
// if (!this->world_stats_.stepping()) {
// break;
// }
if (feedback->remaining_steps == 0) {
break;
}

if (goal_handle->is_canceling()) {
goal_handle->canceled(action_result);
return;
}
}

if (rclcpp::ok()) {
if (feedback->remaining_steps == 0) {
goal_handle->succeed(action_result);
} else {
action_result->result.result = Result::RESULT_OPERATION_FAILED;
action_result->result.error_message = "SimulateSteps was interrupted";
goal_handle->abort(action_result);
}
}
} else {
action_result->result.result = Result::RESULT_OPERATION_FAILED;
action_result->result.error_message = "Unknown error while trying to reset simulation";
goal_handle->abort(action_result);
}
});
thread.detach();
}

SimulationInterfaces::SimulationInterfaces(rclcpp::Node & node)
: dataPtr(gz::utils::MakeUniqueImpl<Implementation>())
{
Expand Down
Loading