I am trying to create node for an "infinite" model/entity spawner for my ROS2 Jazzy + Gazebo Harmonic application. And I am lookinf for Gazebo Harmonic's equivalent to Gazebo11's spawn_entity
service.
I am aware the create_entity.py
of gazebo11 has been replaced with the ros_gz_sim create
node for spawning entities into a Gazebo environment from a launch file. But I am looking for an gz-sim9 alternative for the gazebo11 spawner node which was created like this:
import sys import rclpy from gazebo_msgs.srv import SpawnEntity def request_spawn(xml: str): rclpy.init() node = rclpy.create_node('spawn_entity') client = node.create_client(SpawnEntity, 'spawn_entity') if not client.service_is_ready(): client.wait_for_service() request = SpawnEntity.Request() request.xml = xml future = client.call_async(request) rclpy.spin_until_future_complete(node, future)
I found ros_gz_interfaces.srv
has a SpawnEntity
and a DeleteEntity
srv. And would like to use it similar to this:
#!/usr/bin/env python3
import os
import rclpy
from rclpy.node import Node
from std_msgs.msg import Float64
from geometry_msgs.msg import Pose
from ros_gz_interfaces.srv import SpawnEntity
class ModelSpawner(Node):
"""
Node which spawns models
"""
_l_spawn: float = 0.0
_spawn_frame: str = "box_conveyor"
_min_dist: float = 1.5
_sdf_path: str
_origin: Pose = Pose()
def __init__(self, sdf_path: os.path, origin: Pose) -> None:
super().__init__("gz_spawner_node")
self._sdf_path = sdf_path
self._origin = origin
self._enc_sub = self.create_subscription(
Float64,
"/io/belt_pos",
self.update_box_pos
)
self._client = self.create_client(SpawnEntity, "/spawn_entity") # ???
if not self._client.service_is_ready():
self._client.wait_for_service()
def update_box_pos(self, msg: Float64) -> None:
if (msg.data - self._l_spawn < self._min_dist):
return
request = SpawnEntity.Request()
request.entity_factory.name = "box"
request.entity_factory.sdf = self._sdf_path
request.entity_factory.pose = self._origin
request.entity_factory.relative_to = self._spawn_frame
future = self._client.call_async(request)
rclpy.spin_until_future_complete(self, future)
self._l_spawn = msg.data
Does gz-sim9 (Gazebo Harmonic) have a callable service like spawn_entity
in gazebo11? If yes, which one and how is it used?
Solution
@JRTG's answer was definitely helpful. Writing a GZ Node instead of a ROS Node is way more efficient (if no ROS control is needed). The linked example can easily be used as template. The gz services needed for spawning and despawning are:
/world/<world_name>/create
/world/<world_name>/remove
Something I bumped into was getting gz-transport
working with Python, so maybe people can benefit from my findings:
To install gz-transport
the following steps as in the Gazebo guide:
sudo sh -c 'echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-stable `lsb_release -cs` main" > /etc/apt/sources.list.d/gazebo-stable.list' wget https://packages.osrfoundation.org/gazebo.key -O - | sudo apt-key add - sudo apt-get update sudo apt-get install libgz-transport<#>-dev
With <#>
being the version number you need in my case 14
. This won't get you the Python libs. To install the Python libs add:
sudo apt-get install python3-gz-transport14
This will give you access to gz.transport
and gz.msgs11
in Python3.
Working test code:
#!/usr/bin/env python3
import os
import random
from gz.transport14 import Node
from gz.msgs11.entity_factory_pb2 import EntityFactory
from gz.msgs11.boolean_pb2 import Boolean
from bffp_executables.orientation import euler_to_quaternion
SDF_PATH = <model_path>
WORLD_NAME = <world_name>
def gz_create_entity(world_name: str, sdf_path: os.path, *,
name: str = '', relative_to: str = '',
x: float = 0.0, y: float = 0.0, z: float = 0.0,
yaw: float = 0.0, pitch: float = 0.0, roll: float = 0.0) -> None:
''' Spawns model into Gazebo world'''
node = Node()
service = f"/world/{world_name}/create"
request = EntityFactory()
request.sdf_filename = sdf_path
request.name = name
request.relative_to = relative_to
request.pose.position.x = x
request.pose.position.y = y
request.pose.position.z = z
qx, qy, qz, qw = euler_to_quaternion(yaw, pitch, roll)
request.pose.orientation.w = qw
request.pose.orientation.x = qx
request.pose.orientation.y = qy
request.pose.orientation.z = qz
response = node.request(service, request, EntityFactory, Boolean, 100)
if not response:
raise TimeoutError(f"Call failed: {response.data}")
if __name__ == "__main__":
gz_create_entity(WORLD_NAME, SDF_PATH, name=f"model_{random.random()}")