System-Handle Creation

The soss-core library defines a set of abstract interfaces and provides some utility classes to form a plugin-based framework. A single soss executable instance can connect N middlewares where each middleware has a SOSS-plugin associated with it. The SOSS-plugin, or System-Handle, for a middleware is a lightweight wrapper around that middleware (e.g. a ROS node or a websocket server/client). The soss-core library provides cmake functions that allow these middleware System-Handles to be discovered by the soss executable at runtime after the System-Handle has been installed. Because of this, downstream users can extend SOSS to communicate with any middleware.

A single soss executable can route any number of topics or services to/from any number of middlewares.

SOSS provides built-in System-Handles for connecting to DDS, Orion ContextBroker, ROS, ROS2, and WebSocket. Adding a new System-Handle automatically allows communication with the rest of these protocols.

System-Handle hierarchy

Here you can find a diagram of a System-Handle class inheritance structure.

_images/sh_hierarchy.png

Each System-Handle must inherit, directly or indirectly, from the SystemHandle superclass. Depending on the nature of each protocol, it should implement the derived classes using multiple inheritance from TopicSubscriberSystem, TopicPublisherSystem, ServiceClientSystem, and/or ServiceProviderSystem. To simplify this inheritance, classes TopicSystem, ServiceSystem, and FullSystem are available to inherit from.

System-Handle implementation

In the diagram below, the architecture of a generic “Full” System-Handle and its integration into soss is shown.

_images/sh_impl.png

To ease the implementation, the new system::SystemHandle will inherit from FullSystem. The following sections will explain the methods to be implemented.

To implement the TopicPublisher, ServiceClient, and ServiceProvider interfaces, the most direct way is to create child classes, respectively system::Publisher, system::Client, and system::Server. An additional class system::Subscriber may be useful to manage the subscribers created. In the example shown in the diagram above, the system::SystemHandle will contain the needed instances of these classes, but any approach may be valid if the interfaces are met.

SystemHandle

All System-Handles must implement the configure, okay, and spin_once methods that belong to the superclass:

bool configure(
    const RequiredTypes& types,
    const YAML::Node& configuration,
    TypeRegistry& type_registry);

bool okay() const = 0;

bool spin_once();

The configure method is called to setup the System-Handle with the associated configuration, defined in the YAML file that is passed to it. The types that the SH needs to manage to implement the communication are passed to this method via the types argument, whereas the new types created by the System-Handle are expected to be filled in the type_registry.

The okay method is called by SOSS to check if the System-Handle is working. This method will verify internally if the middleware has any problem.

The spin_once method is called by SOSS to allow spinning to those middlewares that need it.

TopicSubscriberSystem

This kind of system must implement the subscribe method:

using SubscriptionCallback = std::function<void(const xtypes::DynamicData& message)>;

bool subscribe(
    const std::string& topic_name,
    const xtypes::DynamicType& message_type,
    SubscriptionCallback callback,
    const YAML::Node& configuration);

SOSS will call this method in order to create a new subscriber to the topic topic_name using message_type type, plus an optional configuration. Once the middleware system receives a message from the subscription, the message must be translated into the message_type and the System-Handle must invoke the callback with the translated message.

TopicPublisherSystem

This kind of system must implement the advertise method:

std::shared_ptr<TopicPublisher> advertise(
    const std::string& topic_name,
    const xtypes::DynamicType& message_type,
    const YAML::Node& configuration);

SOSS will call this method in order to create a new TopicPublisher to the topic topic_name using message_type type, and optional configuration.

The TopicPublisher is an interface that must be implemented by a Publisher in order to allow SOSS to publish messages to the target middleware. This interface defines a single method publish:

bool publish(const xtypes::DynamicData& message);

When SOSS needs to publish to the middleware system it will call the TopicPublisher::publish method, with a message that must be translated from the message_type parameter by the advertise method above.

ServiceClientSystem

This kind of system must implement the create_client_proxy method:

using RequestCallback =
    std::function<void(
        const xtypes::DynamicData& request,
        ServiceClient& client,
        std::shared_ptr<void> call_handle)>;

bool create_client_proxy(
    const std::string& service_name,
    const xtypes::DynamicType& service_type,
    RequestCallback callback,
    const YAML::Node& configuration);

SOSS will call this method in order to create a new ServiceClient to the service service_name using the service_type type, plus an optional configuration. This ServiceClient will be provided as an argument in the callback invocation when a response is received.

The ServiceClient is an interface that must be implemented by a Client in order to allow SOSS to relate a request with its reply. This is done by providing a call_handle both in the call_service method from ServiceProvider and in the callback from create_client_proxy method. When the reply is received by another System-Handle, its ServiceProvider will call the receive_response method from the Client:

void receive_response(
    std::shared_ptr<void> call_handle,
    const xtypes::DynamicData& response);

The receive_response:

  • Translates the response from service_type and relate the call_handle, if needed, to its middleware’s request;

  • Replies to its middleware.

ServiceProviderSystem

This kind of system must implement the create_service_proxy method:

std::shared_ptr<ServiceProvider> create_service_proxy(
    const std::string& service_name,
    const xtypes::DynamicType& service_type,
    const YAML::Node& configuration);

SOSS will call this method in order to create a new ServiceProvider to the service service_name using the service_type type, plus an optional configuration.

The ServiceProvider is and interface that must be implemented by a Server in order to allow SOSS to request (or call) a service from the target middleware.

void call_service(
    const xtypes::DynamicData& request,
    ServiceClient& client,
    std::shared_ptr<void> call_handle);

This call_service method will translate the request from service_type and will call its middleware service, which stores the related call_handle and client. Once it receives the response from its middleware, it must translate back the response and retrieve the call_handle and client related. Then, it will invoke the receive_response method from the client using the call_handle as argument.

Sequence diagrams

The following diagrams illustrate the previous sections using a generic System-Handle.

TopicPublisher flow

_images/topic_publisher.png

TopicSubscriber flow

_images/topic_subscriber.png

ServiceClient flow

Note that a ServiceClient acts as a client for SOSS and as a server for the middleware.

_images/service_client.png

ServiceProvider flow

Note that a ServiceProvider acts as a server for SOSS and as a client for the middleware.

_images/service_provider.png