Robot Raconteur Core C++ Library
C++ Services

Robot Raconteur is an object-oriented client-service RPC framework. Services expose objects, which are made available to clients using "object references", sometimes referred to as "proxies". See Introduction for an overview of the Robot Raconteur framework.

The object types and their members are defined in "service definition" files. See Service Definitions for more information on how service objects are defined. C++ uses RobotRaconteurGen to generate "thunk" source that implements the object and value types defined in service definitions. See CMake and Robot Raconteur Thunk Source Generation, RobotRaconteurGen Utility, and CMake ROBOTRACONTEUR_GENERATE_THUNK Macro for more information on thunk source and thunk source generation.

For C++, the thunk source generates an abstract class with pure virtual functions for accessing the members. Services implement these abstract interfaces by creating objects that implement the defined members. Robot Raconteur then creates object references and member proxies to allow clients to interact with the members from the remote node.

Developing C++ services is not a trivial task. Because of the design of C++, the creation of security flaws or other terminal bugs is difficult to avoid for novice programmers. The use of Python, C#, or Java is recommended unless C++ is absolutely necessary.

Registering Services

Services are registered with a node using RobotRaconteur::RobotRaconteurNode::RegisterService(). This function takes the name of the service, the name of the service definition of the root object, and the root object instance. The root object must implement an abstract interface generated from a service definition. See Implementing Service Objects. Once registered, the root object will be available to services.

The node must be configured to accept incoming connections. See C++ Node Setup.

The service may have security configured. See Service Security.

Server Context

Each service is managed by an instance of RobotRaconteur::ServerContext. This object interacts with the node to receive and send messages. It manages the service objects and client connections. See RobotRaconteur::ServerContext for the public API. It can be accessed using the RobotRaconteur::RRServiceObject interface, the return from RobotRaconteur::RobotRaconteurNode::RegisterService(), or using the RobotRaconteur::ServerContext::GetCurrentServerContext() function.

Server Endpoints

Each client has a RobotRaconteur::ServerEndpoint associated with it. It can be accessed during member calls using RobotRaconteur::ServerEndpoint::GetCurrentEndpoint(). The server endpoint is used by members to identify a client. The function RobotRaconteur::ServerEndpoint::GetCurrentAuthenticatedUser() can be used to retrieve the authentication for the current server endpoint. To retrieve the uint32_t endpoint ID, use RobotRaconteur::ServerEndpoint::GetCurrentEndpoint()->GetLocalEndpoint().

Service Paths

Each object in a service has a "service path" that uniquely identifies the object. See Service Paths for more information.

Implementing Service Objects

Services expose implementations of object types for use by clients. These implementations contain the real functionaly that the service provides. For instance, a robot driver service object would have members that command the robot. This command can be done through a proprietary API provided by the robot vendor, directly using hardware registers, or one of the other variety of ways that software can interact directly with hardware. The implementation may also be a software component such as a robot planner. The members are used to command the planner to generate robot plans based on parameters passed through the members. Robot Raconteur is not specific about what the implementation of each service object does, rather Robot Raconteur is designed to provide access to the object members from a remote node through a client connection. It is up to the developer of the service to decide what the implemented object actually does. It is also up to the service developer to decide what object, member, and value types need to be used to implement a service. The use of standardized or commonly used service definitions is recommended to increase compatibility between services. Custom service definitions can be created if necessary, but should be avoided if possible.

The thunk source generated by RobotRaconteurGen or the CMake macro contains an "abstract interface" and a "default implementation" for each object type defined in the service definition. The "abstract interface" is a C++ class with pure virtual functions for each member. On the client, these pure virtual functions are implemented to proxy the interactions to the service. On the service side, a class implementing these virtual functions is expected. The implementation of the interface should extend the interface. A "default implementation" is also generated by the thunk source alongside the abstract interface. This is a bare-bones implementation extending the abstract interface. The service developer can extend this "default implementation" instead of extending the abstract interface directly. The developer can use the behavior of the "default implementation", overriding member virtual functions when necessary to change the behavior of the object. The rest of this section assumes that the developer chooses to use the "default implementation".

Note that virtual inheritance must always be used with Robot Raconteur service objects!

Consider the following example service definition:

service experimental.service_example1

object MyObject
    property double my_property
    function void my_function()
end

The generated C++ "abstract interface" thunk source results in the following:

// In namespace experimental::service_example1
// Modified for brevity
class MyObject : public virtual RobotRaconteur::RRObject
{
public:
    virtual double get_my_property()=0;
    virtual void set_my_property(double value)=0;

    virtual void my_function()=0;
};

using MyObjectPtr = boost::shared_ptr<MyObject>;

The service developer is responsible for developing a subclass of MyObject that implements all of the pure virtual functions. The exact form of pure virtual function function for each member is discussed later in this section.

The "default implementation" for MyObject results in the following declaration:

// In namespace experimental::service_example1
// Modified for brevity
class MyObject_default_impl : public virtual MyObject, public virtual RobotRaconteur::RRObject_default_impl
{
protected:
    double rrvar_my_property;

public:
    MyObject_default_impl();
    virtual double get_my_property();
    virtual void set_my_property(double value);

    virtual void my_function();
};

and implementation:

// In namespace experimental::service_example1
// Modified for brevity

MyObject_default_impl::MyObject_default_impl()
{
    rrvar_my_property=0.0;
}

double MyObject_default_impl::get_my_property()
{
    boost::mutex::scoped_lock lock(this_lock);
    return rrvar_my_property;
}
void MyObject_default_impl::set_my_property(double value)
{
    boost::mutex::scoped_lock lock(this_lock);
    rrvar_my_property = value;
}
void MyObject_default_impl::my_function()
{
    throw RobotRaconteur::NotImplementedException("");
}

The "default implementation" is a class named with the object name with _default_impl appended. In the above example, the property my_property and function my_function are implemented by the default implementation. The property implementation stores the value of the property in a class field named with the property named prefixd with rrvar_. The accessors use this_lock for thread locking. this_lock is defined in RobotRaconteur::RRObject_default_impl, which is a virtual base class of all default implementations. Thu function my_function will throw RobotRaconteur::NotImplementedException when invoked. The service developer must implement this virtual function in the subclass of the default implementation.

The following is an example implementation of MyObject that uses the default implementation:

using namespace RobotRaconteur;
using namespace experimental::service_example1

class MyObjectImpl : public virtual MyObject_default_impl
{
public:
    virtual void set_my_property(double value)
    {
        if (value < 0.0)
        {
            throw RobotRaconteur::InvalidArgumentException("my_property must not be negative");
        }

        boost::mutex::scoped_lock lock(this_lock);
        rrvar_my_property = value;
    }

    virtual void my_function()
    {
        double p;
        {
            boost::mutex::scoped_lock lock(this_lock);
            p = rrvar_my_property;
        }
        std::cout << "my_property is currently: " << p << std::endl;
    }
};

This implementation does a bounds check on my_property, and implements my_function to print the current value to the terminal on the service side. Note the use of this_lock to protect rrvar_my_property. This is necessary because member calls on service objects come from the thread pool. Multiple threads may call members concurrently, which can lead to data corruption. Use this_lock to protect data that can be corrupted by concurrent calls. See C++ Multithreading and Asynchronous Functions for more information on the thread pool.

The generated thunk source also provides an "abstract default implementation". The "abstract default implementation" ends with _abstract_default_impl and is intended to be used if a class extends another _default_impl class.

<tt>IRRServiceObject</tt> Interface

Object members such as pipe, wire, and callback need to be initialized by the service before they can be used. Service objects may inherit RobotRaconteur::IRRServiceObjectIntereface to be notified when the object has been initialized. The function RobotRaconteur::IRRServiceObject::RRServiceObjectInit() is called when the initialization is complete.

An example that improves MyObjectImpl to implement IRRServiceObject:

using namespace RobotRaconteur;
using namespace experimental::service_example1

class MyObjectImpl : public virtual MyObject_default_impl, public virtual IRRServiceObject
{
protected:
    boost::weak_ptr<ServerContext> ctx;
    std::string this_service_path;

public:

    virtual void RRServiceObjectInit(boost::weak_ptr<ServerContext> ctx, const std::string& service_path)
    {
        // Save ctx and service_path for later use
        this->ctx = ctx;
        this_service_path = service_path;

        // Do other initialization tasks here
    }

    virtual void set_my_property(double value)
    {
        if (value < 0.0)
        {
            throw RobotRaconteur::InvalidArgumentException("my_property must not be negative");
        }

        boost::mutex::scoped_lock lock(this_lock);
        rrvar_my_property = value;
    }

    virtual void my_function()
    {
        double p;
        {
            boost::mutex::scoped_lock lock(this_lock);
            p = rrvar_my_property;
        }
        std::cout << "my_property is currently: " << p << std::endl;
    }
};

Property Members

Property members allow clients to "get" and "set" a property value on the service object. Properties may use any valid Robot Raconteur value type.

Property members are implemented as two access functions in the object, a "get" and "set" function. The "get" function is the name of member prepended with get_. It takes no arguments, and returns the current value. The "set" function is the name of the member prepended with set_. It takes the new property value, and returns void. Service objects must implement the accesor functions, or use the _default_impl defaul implementation of the property.

For example, the property definition:

property double my_property

An example implementation of the property accessors:

virtual double get_my_property()
{
    boost::mutex::scoped_lock lock(this_lock);

    // Return the current property value here
}
virtual void set_my_property(double val)
{
    boost::mutex::scoped_lock lock(this_lock);

    // Set the property value here
}

Properties can be declared readonly or writeonly using member modifiers. If a property is readonly, the set_ accessor function is not used. If a property is writeonly, the get_ accessor function is not used.

Properties can be implemented as asynchronous functions instead of the synchronous accessors above. The thunk source generates an asynchronous version of the abstract interface class that has asynchronous versions of the property member accessors and function members. The asynchronous interface starts with async_ followed by the object name. The asynchronous accessor functions start with async_get_ and async_set_. If the service object extends the asynchronous interface, the property and function members will always call the asynchronous versions. The async_set_ accessor will not be generated if the property is declared readonly. The async_get_ accessor will not be generated if the property is declared writeonly.

Function Members

Function members allow clients to invoke a function on the service object. Functions may have zero or more value type parameters, and return a value or be declared void for no return. Functions may be "normal", not using a generator, or be "generator functions" which return a generator.

Normal Functions

Normal functions accept zero or more value type parameters, invoke the remote function with these parameters, and return the result, or void. They are implemented in the abstract interface as a C++ function with the same name as the member.

For example, the function definition:

function double addTwoNumbers(int32 a, double b)

An example implementation of the function:

virtual double addTwoNumbers(int32_t a, double b)
{
    return (double)a + b;
}

An example function definition with no parameters and void return:

functon void do_something()

An example implementation of the function:

virtual void do_something()
{
    // Do the operation
}

Functions can be implemented as asynchronous functions instead of synchronous functions. The thunk source generates an asynchronous version of the abstract interface class that has asynchronous versions of function members. The asynchronous interface starts with async_ followed by the object name. The asynchronous function implementation start with async_ followed by the member name. If the service object extends the asynchronous interface, the asynchronous versions of the functions will always be called.

Generator Functions

Generator functions are similar to normal functions, but instead of returning a value or void, they return a generator. A generator is similar to an iterator, or can implement a coroutine. See RobotRaconteur::Generator and Functions for more discussion on generators.

Generators may be Type 1, 2, or 3 depending on the argument and return value configuration for Next(). See Generator Functions for a discussion of these different generator types.

The service implementation of generator functions must return a generator object. This generator object must extend RobotRaconteur::Generator. The client will then be able to call the generator using the generator reference provided to the client. RobotRaconteur::Generator has pure virtual functions for Next(), Abort(), Close(), and the asynchronous versions AsyncNext(), AsyncAbort(), and AsyncClose(). By default, the service will always call the asynchronous versions of these functions. If the service prefers to use synchronous versions of these function, the generator implementation can extend RobotRaconteur::SyncGenerator.

An example service definition containing a generator function to count to 100 with a given start:

service experimental.service_example2

object MyObject
    function int32{generator} count_to_100(int32 start)
end

An implementation of MyObject and the generator returned by count_to_100():

using namespace RobotRaconteur;
using namespace experimental::service_example2;

class CountTo100Generator : public virtual SyncGenerator<int32_t,void>
{
protected:
    int32_t count = 0;
    bool aborted = false;
    bool closed = false;
    boost::mutex this_lock;

public:
    CountTo100Generator(int32_t start)
    {
        count = start;
    }

    virtual int32_t Next()
    {
        boost::mutex::scoped_lock lock(this_lock);

        if (aborted)
        {
            // Throw OperationAbortedException if the generator was aborted
            throw OperationAbortedException("");
        }

        if (closed || count > 100)
        {
            // Throw StopIterationException if generator is complete or closed
            closed = true;
            throw StopIterationException("");
        }

        return count++;
    }

    virtual void Close()
    {
        boost::mutex::scoped_lock lock(this_lock);
        // Close the generator
        closed = true;
    }

    virtual void Abort()
    {
        boost::mutex::scoped_lock lock(this_lock);
        // Abort the generator
        aborted = true;
    }
};

class MyObjectImpl : public virtual MyObject_default_impl
{
public:
    GeneratorPtr<int32_t,void> count_to_100(int32_t start)
    {
        boost::mutex::scoped_lock lock(this_lock);

        if (start < 1 || start > 100)
        {
            throw InvalidArgumentException("start must be between 1 and 100");
        }

        return boost::make_shared<CountTo100Generator>(start);
    }
};

As shown in the above example, the generator should return values until complete. If the generator runs out of values or is closed, it should throw RobotRaconteur::StopIterationException. If the generator is aborted, it should throw RobotRaconteur::OperationAbortedException. Abort should be used in situations where any modifications the generator may have made should not be commited. It can also be used if the generator represents a physical motion, like executing a robot trajectory, that requires the motion to be rapidly aborted.

Event Members

Events are used by the service to notify all connected clients an event has occurred. Events may have zero or more value type parameters. Events are sent to all connected clients. In C++, events are implemented using boost::signals2::signal. See the documentation for boost::signals2::signal for more information on using Boost.Signals2. An example event definition:

event somethingHappened(string what, double when)

The abstract interface generates an accessor for a boost::signals2::signal reference to be returned by the service. The service needs to create an instance of the signal as a field in the class, and return a reference to that instance in the accessor function. The _default_impl class does this automatically, creating a field rrvar_ followed by the event member name.

The following example shows the event being fired from a class extending _default_impl.

// In the same C++ class as the event, extending from the "_default_impl" class

void fire_event(const std::string& what, double when)
{
    boost::mutex::scoped_lock lock(this_lock);
    rrvar_somethingHappened(what, when);
}

The fire_event() function will trigger the signal. The service listens to this signal, and will forward the event to all connected clients.

ObjRef Members

ObjRef members are used to access other objects within a service. See Service Paths for more information on objrefs and service paths. An example objref definition:

objref MyOtherObject other_object

This objref definition results in an accessor function in the abstract interface that needs to be implemented:

class MyOtherObjectImpl : public virtual MyOtherObject
{
    // TODO: Implement the other object
};

// In the object owning the other_object member:

virtual MyOtherObjectPtr get_other_object()
{
    // Return a new object, or a pointer to an existing object
    return boost::make_shared<MyOtherObject>();
};

ObjRefs may also be indexed with an int32_t or string. See ObjRef Members for a discussion of the different forms of the accessor function. The index is passed to the service as a parameter to the accessor function. It is up to the service to decide what object to return based on the index. If the index is invalid, RobotRaconteur::InvalidArgumentException should be thrown.

ObjRefs also use the async_ asynchronous abstract interface if the service object extends it. The asynchronous accessor function is prefixed with async_get_.

Pipe Members

Pipe members provide reliable (or optionally unreliable) data streams between clients and service, in either direction. See RobotRaconteur::Pipe for a discussion of pipes.

An example pipe definition:

pipe double[] sensordata

Results in the following pure virtual functions being generated in the abstract interface that need to be implemented by the service object:

virtual PipePtr<RRArrayPtr<double>> get_sensordata();
virtual void set_sensordata(PipePtr<RRArrayPtr<double>> pipe);

Pipes are initialized by the service when the root service object is registered, or after service objects are returned from objref members. The service will call the set_ accessor function with a pipe server for use by the service. The service object is expected to store a reference to this pipe server, and accept incoming connection requests from clients. The service uses RobotRaconteur::Pipe::SetConnectCallback() to specify a callback function to invoke when clients request pipe connections. Each time a client connects a pipe, the callback is invoked with a RobotRaconteur::PipeEndpointPtr that the service can use to send and/or receive packets with the client. If the service needs to reject the incoming pipe connection, the callback can throw an exception. This exception will abort the connection, and pass the exception back to the client. The service is responsible for maintaining a reference to the RobotRaconteur::PipeEndpointPtr, but the reference is still owned by the pipe. It is recommended that boost::weak_ptr<RobotRaconteur::PipeEndpoint<T>> be used to store the endpoint pointer to avoid memory leaks.

Pipes declared readonly may only send packets on the service side. Pipes declared writeonly may only receive packets on the service side.

There is a helper class RobotRaconteur::PipeBroadcaster that can be used when the service needs to send the same packets to every connected client. It should only be used with pipes declared readonly. The pipe broadcaster has optional flow control, that tracks how many packets are "in flight", and compares it to the "maximum backlog". If the number of packets in flight exceeds the maximum backlog, sending packets is paused. (This flow control method only works with reliable pipes.)

The _default_impl will automatically create a RobotRaconteur::PipeBroadcaster for readonly pipes. The pipe broadcaster is stored in a field rrvar_ followed by the name of the pipe member.

An example, assuming that the sensordata pipe is in MyObject, and using IRRServiceObject to set the maximum backlog:

class MyObjectImpl : public virtual MyObject_default_impl, public virtual IRRServiceObject
{
public:

    virtual void RRServiceObjectInit(boost::weak_ptr<ServerContext> ctx, const std::string& service_path)
    {
        // Set the maximum backlog to prevent overloading the transport
        rrvar_sensordata->SetMaxBacklog(3);
    }

protected:

    void SendData(RRArrayPtr<double> data)
    {
        boost::mutex::scoped_lock lock(this_lock);

        // Test to make sure that rrvar_sensordata has been initialized
        if (rrvar_sensordata)
        {
            // Send the packet, don't wait for send completion
            rrvar_sensordata->AsyncSendPacket(data,[]());
        }
    }
};

AsyncSendPacket() is used to prevent the SendData() function from blocking the thread.

If the pipe member is not marked readonly, the _default_impl will store RobotRaconteur::PipePtr in rrvar_. Use RobotRaconteur::Pipe::SetConnectCallback() in RRServiceObjectInit to set the connect callback function.

Callback Members

Callbacks allow the service to invoke a function on a specific client. The definition is nearly identical to a function member, except the keyword is callback and generators are not supported. An example callback definition:

callback double addTwoNumbersOnClient(int32 a, double b)

The callback is managed by an instance of RobotRaconteur::Callback. The example results pure virtual functions being generated in the abstract interface that need to be implemented by the service object:

virtual CallbackPtr<boost::function<double (int32_t, double)> > get_addTwoNumbersOnClient();
virtual void set_addTwoNumbersOnClient(CallbackPtr<boost::function<double (int32_t, double)> > callback);

The service object needs to store the RobotRaconteur::CallbackPtr in a field so it can retrieve proxies to client callbacks. The _default_impl automatically handles this, and stores the RobotRaconteru::CallbackPtr in a field rrvar_ followed by the member name.

Service objects retrieve proxies to the client callbacks using RobotRaconteur::Callback::GetClientFunction(). This function takes a uint32_t client endpoint ID to select which client to call. This client endpoint ID can be determined using RobotRaconteur::ServerEndpoint::GetCurrentEndpoint()->GetLocalEndpoint() called during a function member or property member request.

An example service definition demonstrating using a callback:

service experimental.service_example3

object MyObject
    function void other_function()
    callback double addTwoNumbersOnClient(int32 a, double b)
end

An implementation of addTwoNumbersOnClient() that will call the last client to invoke other_function():

using namespace ::experimental::service_example3;

class MyObjectImpl : public virtual MyObject_default_impl
{
    uint32_t client_id = 0;

public:
    virtual void other_function()
    {
        // Store the endpoint ID
        boost::mutex::scoped_lock lock(this_lock);
        client_id = ServerEndpoint::GetCurrentEndpoint()->GetLocalEndpoint();
    }

protected:
    double invoke_add(int32_t a, double b)
    {
        boost::function<double(int32_t,double)> cb;
        {
            boost::mutex::scoped_lock lock(this_lock);
            try
            {
                // Get a proxy to the client callback
                cb = rrvar_addTwoNumbersOnClient->GetClientFunction(client_id);
            }
            catch (std::exception&)
            {
                // If getting the callback failed, set client_id to zero, or invalid
                client_id = 0;
                throw InvalidOperationException("Client ID is not valid");
            }
        }
        return cb(a,b);
    }
};

The above example will invoke the callback on the last function to call other_function(). The choice of client which client to use depends completely on the purpose of the callback. The callback can be invoked on any connected client. If the client has not specified a callback for use, a RobotRaconteur::InvalidOperationException is thrown.

Wire Members

Wire members provide a "most recent" values. They are typically used to communicate a real-time signal, such as a robot joint angle. See RobotRaconteur::Wire for a discussion of wires.

An example wire definition:

wire double[2] currentposition

Results in the following functions pure virtual being generated in the abstract interface:

virtual WirePtr<RRArrayPtr<double>> get_currentposition();
virtual void set_currentposition(WirePtr<RRArrayPtr<double>> wire);

These functions must be implemented by the service object.

Implementing wires can be somewhat complicated. The use of the helper classes RobotRaconteur::WireBroadcaster and RobotRaconteur::WireUnicastReceiver are recommended. Using these two helper classes will be discussed first, followed by a discussion of using the wire without helper classes.

Wires can be marked as readonly or writeonly. If neither is specified, the wire can send values in both directions. Wire memers are usually set to readonly or writeonly for most service designs. While using a full-duplex wire is possible, the need for full-duplex wires is not typical. readonly wires can only send values from service to client. writeonly wires can only send values from client to service.

For readonly wires, the service can use the RobotRaconteur::WireBroadcaster to send the same values to all connected wires. The OutValue is set on the wire broadcaster, and this value is sent to all connected wires. For writeonly wires, the service can use the RobotRaconteur::WireUnicastReceiver. The unicast receiver is desigen to provide the InValue of the most recent wire connection to connect. This InValue is set by the client, providing values from the client to the service. If a client wire connection is already established, it is closed in favor of the more recent connection. Clients locking should be used to prevent other clients from connecting. See Object Locking. The RobotRaconteur::WireBroadcaster and RobotRaconteur::WireUnicastReceiver automate most of the functionality of the wire. In most cases the user simply needs to set the OutValue of the broadcaster and query the InValue of the receiver.

The generated _default_impl class implements the accessor functions for the wires, and stores the wire in class fields named rrvar_ appended with the name of the member. The _default_impl has special behavior for readonly and writeonly members. For readonly wire members, the rrvar_ field will automatically be initialized with a RobotRaconteur::WireBroadcaster. For writeonly wire members, the rrvar_ field will automatically be initialized with a RobotRaconteur::WireUnicastReceiver. (The service can override the get_ and set_ accessor functions to override this behavior.)

Wires are typically used with a (soft) real-time control loop, such as a robot feedback loop. The following is a simple example of a first-order discrete-time system y[n] = -a*y[n-1] + b*u[n] loop implemented using wires for input and output.

Discrete time loop service definition:

service experimental.service_example4

object MyObject
    wire double y [readonly]
    wire double u [writeonly]
end

The service object implementation of the discrete time system:

class MyObjectImpl : public virtual MyObject_default_impl, public virtual IRRServiceObject
{
    double y_n1 = 0;
    double a = 0.1;
    double b = 0.1;
public:

    virtual void RRServiceObjectInit(boost::weak_ptr<ServerContext> ctx, const std::string& service_path)
    {
        rrvar_u->SetInValueLifespan(250);
    }

    // Run one step of the discrete time system
    void Step()
    {
        boost::mutex::scoped_lock lock(this_lock);

        // If either wire has not been initialized, return
        if (!rrvar_y || !rrvar_u)
        {
            return;
        }

        // Get "u", if not available, use zero
        double u = 0;
        TimeSpec ts;
        uint32_t ep;

        // rrvar_u is initialized to WireUnicastReceiver<double> by MyObject_default_impl
        if (!rrvar_u->TryGetInValue(u,ts,ep))
        {
            // Set u to zero if no input available
            u = 0;
        }

        double y = - a * y_n1 + b * u;

        // rrvar_y is initialized to WireBroadcaster<double> by MyObject_default_impl

        // Set the out value wire
        rrvar_y->SetOutValue(y);

        // Save y for next iteration
        y_n1 = y;
    }
};

The RobotRaconteur::WireUnicastReceiver::SetInValueLifespan() is used to give the received input value a finite lifespan. If the client disconnects or stops sending data, the in value will expire, preventing stale data from being received. The Step() function must be called periodically by the program. This is typically done in a loop in main() or from a thread.

For a device like a robot or a senser, the wires would be used to send feedback and receive commands instead of being used with a software discrete time system.

The discussion and examples so far have used the helper classes WireBroadcaster and WireUnicastReceiver. These classes automatically manage the incoming wire connections, peek requests, and poke requests. If the service does not want to use a helper class, it must implement the get_ and set_ accessors in the object, and it must set callbacks for incoming wire connections, peek in value requests, peek out value requests, and poke out value requests. The relevant functions to set the callbacks are RobotRaconteur::Wire::SetWireConnectCallback(), RobotRaconteur::Wire::SetPeekInValueCallback(), RobotRaconteur::Wire::SetPeekOutValueCallback(), and RobotRaconteur::Wire::SetPokeOutValueCallback(). These callbacks are usually configured in the set_ accessor. The set_ accessor will only be called once by the service. Manually managing wire connections and peek/poke callbacks is not normally necessary, since the helper classes can be used directly or subclassed to implement wire functionality.

Memory Members

Memories are used to read and write a memory segment on the service. Memories may be numeric arrays, numeric multidimarrays, pod arrays, pod multidimarrays, namedarray arrays, or namedarray multidimarrays. The different types of memories and their corresponding C++ classes are discussed here: Memory Members.

A numeric array memory client and a numeric multidimarray memory client will be used as examples. Pod and namedarray memories are identical, except for the memory class and the value types being utilized.

Example array memory definition:

memory double[] datahistory

Results in a single pure virtual accessor function being generated in the abstract interface that must be implemented:

virtual ArrayMemoryPtr<double> get_datahistory()
{
    // Assume that there is a field storing the data
    RRArrayPtr<double> my_data = self->my_data_;

    // Return an array memory
    return boost::make_shared<ArrayMemory>(my_data);
}

The service will proxy the read and write requests to the returned memory. The service can return the existing memory C++ classes, however these do not provide any locking or data protection. Is is recommended that the provided C++ classes be extended for the specific needs of the application.

Memory members are a seldomnly used feature. They should only be used when a device provides a true shared memory region, such as a ring buffer or a set of registers that must be exposed, or when there is a very large data set that is randomnly accessed by clients.

Service Security

Services can be secured using a RobotRaconteur::ServiceSecurityPolicy instance. The security policy is passed to RobotRaconteur::RobotRaconteurNode::RegisterService() function when the service is registered. The constructor takes a map of policies, and a pointer to a user authenticator. Currently, the only authenticator available is RobotRaconteur::PasswordFileUserAuthenticator. This authenticator takes a file or string of usernames, passwords, and privileges to authenticate against. See RobotRaconteur::PaswordFileUserAuthenticator for information on the file format. See Security for a discussion of Robot Raconteur security.

The following is a typical example of a secured service being initialized:

// Username, password, and privileges data, one user per line. Passwords md5 hashed
std::string password_auth_data =
    "user1 79e262a81dd19d40ae008f74eb59edce objectlock" "\n"
    "user2 309825a0951b3cf1f25e27b61cee8243 objectlock" "\n"
    "superuser1 11e5dfc68422e697563a4253ba360615 objectlock,objectlockoverride" "\n";

// Create the service object
MyObjectPtr obj = boost::make_shared<MyObjectImpl>();

std::map<std::string,std::string> policies = {
    {"requirevaliduser", "true"},
    {"allowobjectlock", "true"}
};

// Create the password authenticator
PasswordFileUserAuthenticatorPtr auth = boost::make_shared<PasswordFileUserAuthenticator>(password_auth_data);

// Create the security policy
ServiceSecurityPolicyPtr s = boost::make_shared<ServiceSecurityPolicy>(auth,policies);

// Register the service
RobotRaconteurNode::s()->RegisterService("my_service", "experimental.service_example5", obj);

The above example has two normal users, "user1" and "user2", and one superuser, "superuser1". The superuser has the "objectlockoverride" privilege, allowing the superuser to unlock any object regardless of which user created the lock. The passwords are stored as md5 hashes. These md5 hashes can be generated using RobotRaconteurGen. See RobotRaconteurGen Utility. The password data should be stored in a file so it can be modified as users are added and removed.

Object Locking

Clients can request object locks to gain exclusive access. There are three types of object locking: user locks, client locks, and monitor locks. See object_locking and Object Locking for more information on object lock types.

User Locks and Client Locks

User locks and client locks only require that the "allowobjectlock" policy is set, that the current client is authenticated, and the authenticated client has the "objectlocking" privilege. Object locking at this point is handled automatically by the service. The service can create and release locks on behalf of clients using RobotRaconteur::ServerContext::RequestObjectLock(), RobotRaconteur::ServerContext::RequestClientObjectLock(), and RobotRaconteur::ServerContext::ReleaseObjectLock(). See each function for more information.

Monitor Locks

Monitor locks are used to request a thread-exclusive lock. Service objects that wish to be monitor-lockable must extend and implement RobotRaconteur::IRobotRaconteurMonitorObject. This interface contains function to enter the lock, and exit the lock. The simplest implementation will use a boost::mutex to implement the lock. The following example uses this method:

using namespace RobotRaconteur;

class MyObjectImpl : public virtual MyObjectDefaultImpl,
    public virtual IRobotRaconteurMonitorObject
{
public:
    virtual void RobotRaconteurMonitorEnter()
    {
        monitor_lock.lock();
    }

    virtual void RobotRaconteurMonitorEnter(int32_t timeout)
    {
        if (timeout==-1)
        {
            RobotRaconteurMonitorEnter();
        }
        else
        {
            monitor_lock.timed_lock(boost::posix_time::milliseconds(timeout));
        }
    }

    virtual void RobotRaconteurMonitorExit()
    {
        monitor_lock.unlock();
    }

protected:
    boost::mutex monitor_lock;
};

The service can also use monitor_lock directly, when it needs to prevent clients from accessing a memory region.

Not that unlike client and user locks, monitor locks are not enforced. The client must voluntarily request monitor locks.

Service Attributes

Service attributes are provided by services to help with discovery. The attributes are made available to clients during the discovery process. In C++, the attributes have the type std::map<std::string,RRValuePtr>. The attributes follow the same type rules as varvalue{string}. The attributes map must not contain any types defined in service definitions, since the client won't be able to unpack these types.

An example of using attributes for a service with root object type MyRobot:

MyRobotPtr robot = boost::make_shared<MyRobot>();
std::map<std::string,RRValuePtr> attributes =
{
    { "description", stringToRRArray("My awesome robot!") },
    { "location", stringToRRArray("Robotics lab") }
};

ServerContextPtr ctx = RobotRaconteurNode::s()->RegisterService("my_robot", "experimental.my_robot", robot);
ctx->SetAttributes(attributes);

Releasing Objects

When service objects are returned from objref members, the service takes ownership of the object. If the service needs to release the object, it must be done explicitly using the RobotRaconteur::RobotRaconteurNode::ReleaseServicePath() function. This function takes the "service path" of the object to be released. See Service Paths for more information on service paths. The service path of an object can be determined using RobotRaconteur::IRRServiceObject, or using the function RobotRaconteur::ServerContext::GetCurrentServicePath(). When the service path is released, all connected clients are notified using an event. If the service path contains sensitive data such as a session token, the RobotRaconteur::ServerContext::ReleaseServicePath(boost::string_ref path, const std::vector<uint32_t>& endpoints) overload should be used. This version will only notify the clients specified in the endpoints parameter.