Warning
Warning: Unity Simulation is deprecated as of December 2023, and is no longer available.
Communicating with external processes
In the previous section you installed Simulation-Foundation and Simulation-ROS-Integrations packages. This section helps you set up the communication between Unity and ROS using those packages.
Simulation-Foundation is the interfaces needed to communicate with an external process or service. (Herein, we will use "service" to mean any external process, service, or similar.) These interfaces are agnostic and can be implemented for ROS, GRPC etc.
Simulation-ROS-Integrations is an implementation of those interfaces needed to communicate with ROS.
In this section we discuss the components/interface we use to communicate with an external service.
Interface/BaseClass | Function |
---|---|
BaseConnectionComponent | BaseConnectionComponent is a MonoBehaviour class to configure and instantiate communication with the external service. It exposes the IConnector settings to the user via Inspector settings. It will then instantiate an IConnector instance at runtime. |
IConnector | The IConnector interface is used to establish communication with the external service and then handle all communication with that service. An IConnector class implements methods to: 1. Create or get an IPublisher used to publish messages to a topic to be read by an external service. 2. Subscribe a callback to read messages from a topic sent by an external service. 3. Implement a listener to handle service requests. 4. Create a service client to send requests to a service. |
IPublisher | The IPublisher interface is used to publish messages to a topic to be read by an external service. |
IServiceClient | The IServiceClient interface is used to send service requests to an external application and receive a corresponding response. |
Adding a BaseConnectionComponent
First we add a BaseConnectionComponent
to the Unity scene.
RosEndpointConnectorComponent
is a derived class of BaseConnectionComponent
. Add this component to the scene to communicate with ROS. RosEndpointConnectorComponent
is contained in Simulation-ROS-Integrations.
- Create an empty GameObject in the scene. Rename it
RosEndpointConnector
. - Click on
Add Component
in the inspector window and type the nameROS Endpoint Connector Component
. - Add the
RosEndpointConnectorComponent
from the list. - Set the
Endpoint IP address
andEndpoint Port
to the IP address and port of the machine running ROS. - Make sure
Connect On Start
is checked.
Getting a Reference to the IConnector Interface in Your Script
Now you have a RosEndpointConnectorComponent
in your scene. We can use it to get a reference to the IConnector
interface to send and receive messages to ROS. There are two methods to get a reference to IConnector
interface.
Using RosEndpointConnector
The RosEndpointConnectorComponent
contains the reference to the IConnector
interface. If you know which GameObject
contains the RosEndpointConnectorComponent
(RosEndpointConnector
, from above), you can follow these steps to get the IConnector
instance.
- Use the GetComponent API to get the
RosEndpointConnectorComponent
. - Use
GetConnector
API to get theIConnector
interface.
The following code block illustrates this method.
public class NewScript : MonoBehaviour
{
GameObject rosGameObject;
RosEndpointConnectorComponent rosComponent;
IConnector rosConnector;
void Start()
{
rosComponent = rosGameObject.GetComponent<RosEndpointConnectorComponent>();
var rosConnector = rosComponent.GetConnector();
}
}
Note that, while this works fine, it is not the recommended way to fetch a reference to the Connector as it tightly couples your script to that specific Connector instance. Instead, consider using the ConnectorInjector interface.
Using ConnectorInjector
FindConnector
The ConnectorInjector
class in Simulation-Foundation makes it easier to get the IConnector
interface from any script in the project. Use the FindConnector
API in ConnectorInjector
to get the IConnector
. You pass a GameObject
or MonoBehaviour to the FindConnector
API. It searches down, and then up the GameObject
tree hierarchy, looking for a BaseConnectionComponent
component and returns its IConnector
interface.
If a BaseConnectionComponent
cannot be found in the GameObject
tree hierarchy, it returns the default IConnector
from the default BaseConnectionComponent
.
Here is a code block on how to use ConnectorInjector
from any MonoBehaviour.
var myConnector = ConnectorInjector.FindConnector(this);
Multiple Connectors
FindConnector will respond dynamically to the composition of your scene. If you want to change the Connector a specific GameObject in the scene is using, simply add a new Connector to that GameObject's hierarchy. You may use a ProxyConnector to point your Object at a specific Connector not in the Object's hierarchy, or create a Connector per Object. The only restriction is that no two Connectors should contain the same connection information (e.g. you cannot create two RosEndpointConnectors that both connect to the exact same IP:Port). If you need that functionality, use the ProxyConnector to bind both objects to the shared connection.
Using ConnectorInjector.GetDefaultConnector
The ConnectorInjector
class provides the GetDefaultConnector
API to return the default IConnector
. It will search the scene for the all BaseConnectionComponent
components and return the first one found.
The default IConnector
can also be set with SetDefaultConnector
:
SetDefaultConnector(IConnector connection)
SetDefaultConnector(BaseConnectionComponent connection)
var rosConnector = ConnectorInjector.GetDefaultConnector();
Sending and Receiving Messages Using the IPublisher Interface and Subscribe Method
Now that you have an IConnector
interface, you can send and receive messages. IConnector
provides APIs to register a publisher or subscriber to a specific topic.
Subscribing to Messages from an External Service
IConnector
provides the Subscribe
API to subscribe to a topic.
In this example, we subscribe to a topic called RobotPose
with the message type Pose
. Whenever the IConnector
receives a message, it passes the message to the PoseCallback
callback function.
void PoseCallback(PoseMsg message){}
void Update()
{
// Option 1
rosConnector.Subscribe<PoseMsg>("RobotPose", PoseCallback);
// Option 2
rosConnector.Subscribe("RobotPose", PoseMsg.MessageTypeName, PoseCallback);
}
Publishing Messages to an External Service
IConnector
provides the RegisterPublisher
API to create or get an IPublisher
interface to publish to a topic. The IPublisher
interface provides the Publish
API to publish a message.
In this example, we publish a PoseMsg
to the RobotPose
topic.
// Registering the IPublisher
// Option 1
IPublisher posePublisher = rosConnector.RegisterPublisher<PoseMsg>("RobotPose")
// Option 2
IPublisher posePublisher = rosConnector.RegisterPublisher("RobotPose",PoseMsg.MessageTypeName)
// Publish a Message
PoseMsg sampleMsg = new PoseMsg();
posePublisher.Publish(sampleMsg);
Sending and Receiving Service Requests and Response Using the ImplementService Method and IServiceClient Interface
Hosting a Service in Unity
IConnector
provides the ImplementService
API to host a service in Unity. It receives an IMessage
type request and responds with another IMessage
type.
In this example, we implement a service named "exampleService" that receives a TransformMsg
request and responds with a PoseMsg
.
// Creating a service in Unity
// Option 1
IConnector exampleConnector = ConnectorInjector.FindConnector(gameObject);
exampleConnector.ImplementService<TransformMsg,PoseMsg>("exampleServicde",exampleFunction);
// Option 2
exampleConnector.ImplementService("exampleService", TransformMsg.k_MessageTypeId, PoseMsg.k_MessageTypeId, exampleFunction);
PoseMsg exampleFunction(TransformMsg arg1)
{
return new PoseMsg();
}
Creating a Service Client in Unity
IConnector
provides the RegisterServiceClient
API to create a service client in Unity. It returns an IServiceClient
interface. You can use its SendRequest
API to send an IMessage
request to a service and receive the IMessage
response.
In this example, we create a client to a service named "exampleService". We send a TransformMsg
request to this service and receive a PoseMsg
response.
IConnector exampleConnector = ConnectorInjector.FindConnector(gameObject);
IServiceClient serviceClient = exampleConnector.RegisterServiceClient<TransformMsg,PoseMsg>("exampleService");
exampleConnector.RegisterServiceClient("exampleService", TransformMsg.k_MessageTypeId, PoseMsg.k_MessageTypeId);
// Option 1
serviceClient.SendRequest<TransformMsg, PoseMsg>(new TransformMsg(), exampleCallback);
// Option 2
serviceClient.SendRequest(new TransformMsg(), exampleCallbackIMessage);
// Option 3
Task<PoseMsg> result = serviceClient.SendRequest<TransformMsg, PoseMsg>(new TransformMsg());
PoseMsg response = result.Result;
// Option 4
Task<IMessage> result1 = serviceClient.SendRequest(new TransformMsg());
PoseMsg response1 = (PoseMsg)result1.Result;
void exampleCallback(PoseMsg msg)
{
}
void exampleCallbackIMessage(IMessage msg)
{
}
IPublisher/Subscribe vs IServiceClient/ImplementService
Both IPublisher and IServiceClient will send a message to an external service.
IPublisher will publish the message but not wait for a response.
IServiceClient will send the request and wait for a response.
Updated 2022-08-31T23:11:26.000Z