Robot Raconteur Core C++ Library
Service Definitions

The following example code shows the code contained in the "experimental.create2.robdef" file. It is a service definition. Service definition files are plain text files that describe the object types and value types (data types). Object types are references, meaning that on the client they are simply an advanced reference (sometimes called a "proxy") to the service. Value types are the actual data that are transmitted between client and service. They are always passed by value, meaning that they are copied between the client or service when transmitted.

#Service to provide sample interface to the iRobot Create
service experimental.create2

stdver 0.10

struct SensorPacket
    field uint8 ID
    field uint8[] Data
end

object Create
    constant int16 DRIVE_STRAIGHT 32767
    constant int16 SPIN_CLOCKWISE -1
    constant int16 SPIN_COUNTERCLOCKWISE 1

    function void Drive(int16 velocity, int16 radius)

    function void StartStreaming()
    function void StopStreaming()

    property int32 DistanceTraveled [readonly]
    property int32 AngleTraveled [readonly]
    property uint8 Bumpers [readonly]

    event Bump()

    wire SensorPacket packets [readonly]

    callback uint8[] play_callback(int32 DistanceTraveled, int32 AngleTraveled)
end

The first line in the service definition contains the keyword service followed by the name of the service type. The names of services follow similar rules to Java package names. For experimental software, the name should be prefixed with experimental, for example experimental.create2. For hobbyists and standalone software, the name should be prefixed with community and your username, for example community.myusername.create, where myusername is replaced with your robotraconteur.com username. If a domain name for an organization is available it can be used in the same way as Java packages, for example com.wasontech.examples.create2. Unless you have valid ownership of a domain, experimenta or community should be used.

Next in the service there should be stdver and the minimum version of Robot Raconteur required to access the service. For now this should be 0.10. Example createinterface does not show it, but there can also be one or more import to reference structures and objects in other service definitions. The rest of service definition defines the structures and objects of the service definition. (Lines starting with # are comments.)

Names

User-defined names are used throughout service definitions. These names must:

  • Contain only letters, numbers, and underscores
  • Not begin with a number
  • Not begin or end with an underscore
  • Not begin with "rr" or "robotraconteur" in any upper and lower case combination
    • Service name segments are exempt from this rule
  • Not begin with:
    • get_
    • set_
    • async_
  • Not be a keyword

The following keywords are reserved:

object end option service struct import implements field property function event objref pipe callback wire memory void int8 uint8 int16 uint16 int32 uint32 int64 uint64 single double string varvalue varobject exception using constant enum pod namedarray cdouble csingle bool stdver

Value Types

Value types are the data that are passed between the client and service. Value types can be primitives, structures, pods, namedarrays, maps, lists multidimensional arrays, or enums.

Primitives

Primitives consist of scalar numbers, single dimensional number arrays, and strings. The table below contains the primitives that are available for use. Primitive numerical types can be turned into arrays by appending brackets [] to the end, for example int32[] is an array of 32 bit signed integers. If a fixed size array is desired, a number can be included between the brackets for the desired array size, for example int32[8] has a fixed length of 8 integers. If an array is desired that has a maximum size, a - sign can be included in the brackets, for example int32[100-] can have up to 100 integers. Strings are always arrays so the brackets are not valid. The void type is only used for function declarations that do not have a return value.

Type Bytes/Element Description
void 0 Void
double 8 Double precision floating point
single 4 Single precision floating point
int8 1 Signed 8-bit integer
uint8 1 Unsigned 8-bit integer
int16 2 Signed 16-bit integer
uint16 2 Unsigned 16-bit integer
int32 4 Signed 32-bit integer
uint32 4 Unsigned 32-bit integer
int64 8 Signed 64-bit integer
uint64 8 Unsigned 64-bit integer
string 1 UTF-8 string
cdouble 16 Complex double precision floating point
csingle 8 Complex single precision floating point
bool 1 Logical boolean

Structures

Structures are collections of value types; structures can contain primitives, other structures, maps, or multidimensional arrays. The example experimental.create2 service definition shows the definition of the structure SensorPacket. A structure is started with the keyword struct followed by the structure name. It is ended with the end keyword. The entries in the structure are defined with the keyword field followed by the type, and finally the name of the field. If a structure from a different service definition is used, first the referenced service definition is imported at the top of the service definition and the structure is referenced by the external service definition dot the name of the structure.

Pods

Pods (short for "plain-old-data") are similar to structures, but are more restricted to ensure they have the same size. All data stored in pods are stored contiguously (c-style), while structs use pointers to the data. Pods can only contain pods, arrays of pods (fixed or max length), namedarrays, and namedarrays arrays (fixed or max length). Only numeric primitives may be used; strings, structs, lists, and maps may not be stored in pods. A pod is started with the keyword pod followed by the pod name. It is ended with the end keyword. The entries in the pod are defined with the keyword field followed by the type, and finally the name of the field. If a pod from a different service definition is used, first the referenced service definition is imported at the top of the service definition and the pod is referenced by the external service definition dot the name of the pod. Pods can be used with arrays and multi-dim arrays.

Namedarrays

Namedarrays are a union type designed to store numeric arrays that also have specific meanings attached to each entry. An example is a 3D vector. The vector can either be viewed as a 3x1 array, or as a structure containing three fields, (x,y,z). A namedarray stores the contained data as a primitive array, but allows the data to be viewed as a structure. Namedarrays should be used when possible since they have the most compact memory format. Namedarrays can only contain numeric primitives, fixed numeric primitive arrays (no multidimarrays), other namedarrays (with the same numeric type), and fixed arrays of namedarrays. A namedarray is started with the keyword namedarray followed by the namedarray name. It is ended with the end keyword. The entries in the namedarray are defined with the keyword field followed by the type, and finally the name of the field. If a namedarray from a different service definition is used, first the referenced service definition is imported at the top of the service definition and the namedarray is referenced by the external service definition dot the name of the namedarray. Namedarrays can be used with arrays and multi-dim arrays.

Maps

Maps can either be keyed by int32 or string. In other languages they would be called Dictionary, Hashtable, or Map. The contained data is a value type, but the contained data may not be another container type (map or list). They are created with curly braces. For example, string{int32} would be a map of strings keyed by an integer. string{string} would be a map of strings keyed by another string. SensorPacket{string} and int32[]{int32} are also valid examples. string{int32}{int32} is not valid. There can only be one dimension of map keying and/or lists.

Lists

Lists follow similar rules to maps. They are created with curly braces. For example, string{list} would be a list of strings. SensorPacket{list} and int32[]{list} are also valid examples. string{list}{list} is not valid. There can only be one dimension of lists and/or map keying.

Multidimensional Arrays

The multidimensional arrays allow for the transmission of real or complex matrices of any dimension. They are defined by putting a * inside the brackets of an array. For example, double[*] defines a multidimensional array of doubles. Multidimensional arrays can also have fixed dimensions. For example double[3,3] defines a 3x3 matrix. The dimensions are in matrix (column-major) order.

Enums

Enums are a special representation of int32 that names each value. Enums are aliases, with the value be stored as int32 internally. An enum is started with the keyword enum followed by the enum name. It is ended with the end keyword. The values are specified with a name = value format, separated by commas. Values can be signed integers, unsigned hexadecimal, or omitted to implicitly increment from the last value.

enum myenum
    value1 = -1,
    value2 = 0xF1,
    value3,
    value4
end

varvalue

In certain situations it may be desirable to put in a "wildcard" value type. The varvalue type allows this. Use with caution!

Note: structs, maps, lists, and varvalue can be null. All other types are non-nullable. (NULL, None, etc. depending on language).

Object Types

Objects begin with the keyword object followed by the name of the object, and closed with the keywords end. Objects have members that implement functionality. Within Robot Raconteur there are eight types of members: Properties, Functions, Events, ObjRefs, Pipes, Callbacks, Wires, and Memories . They are defined between object and end, with one member per line.

Properties

Keyword: property

Properties are similar to class variables (fields). They can be written to (set) or read from (get). A property can take on any valid value type. A property is defined within an object with the keyword property followed by the value type of the property, and finally the name of the property. (All member names within an objectmust be unique). An example:

property double myvar

Properties can use modifiers readonly, writeonly, urgent, and/or perclient. See modifiers.

Functions

Keyword: function

Functions take zero or more value type parameters, and return a single value type. The parameters of the functions must all have unique names. The return value of the function may be void if there is no return. A function is defined by the keyword function followed by the return type, followed by the name of the function. The parameters follow as a comma separated list of parameter type and parameter name. The parameter list is enclosed with parenthesis. An example:

function double addTwoNumbers(int32 a, double b)

Functions can also return a "generator", which is a form of iterator. (These generators are modeled after Python generators.) This is useful for long running operations or to return large amounts of data. Generators take three forms. The first is when each iteration of the generator takes a parameter and returns a value. This takes the form:

function double{generator} addManyNumbers(int32 a, double{generator} b)

In this example, the a parameter is sent with the function call, while b and return are sent and received using the Next function of the generator.

The next form of the generator returns a value each iteration of the generator.

function double{generator} getSequence(int32 a, double b)

In this example, a and b are sent with the function call, and return is returned using the Next function of the generator.

The last form takes a parameter each iteration.

function void accumulateNumbers(double{generator} b)}

Note that the generator return must be void or a generator type. Each call to Next will receive a parameter.

Generators will throw either StopIterationException to signal that the generator is finished, or it will throw OperationAbortedException to signal that there was an error and the generator should be destroyed. Generators clients must call Close or Abort on a generator if a StopIterationException or other exception is not received.

Generators that represent long running operations should return from Next with updated status information at least every 10 seconds to prevent timeout errors.

Functions can use the urgent modifier. See modifiers.

Events

Keyword: event

Events provide a way for the service to notify clients that an event has occurred. When an event is fired, every client reference receives the event. How the event is handled is language-specific. An event is defined similar to a function, however there is no return. The parameters are passed to the client. There is no return. An example:

event somethingHappened(string what, double when)

Note that events do not have flow control, so they should be used sparingly.

Events can use the urgent modifier. See modifiers.

Object References

Keyword: objref

A service consists of any number of objects. The root object is the object first referenced when connection to a service. The other object references are obtained through objref members. These members return a reference to the specified object. An objref is defined by the keyword objref followed by the object type followed by the objref member name. The object type can be varobject to return any valid object type (Use with caution!). The objref can also be indexed by number ([],{int32}) or by string ({string}). This returns a different reference based on the index. It does not return a set of references. An example:

objref mysubobj anotherobj{string}

If an object from a different service definition is used, first the referenced service definition is imported at the top of the service definition and the object is referenced by the external service definition dot the name of the object.

Pipes

Keyword: pipe

Pipes provide full-duplex first-in, first-out (FIFO) connections between the client and service. Pipes are unique to each client, and are indexed so that the same member can handle multiple connections. The pipe member allows for the creation of PipeEndpoint pairs. One endpoint is on the client side, and the other is on the server side. For each connected pipe endpoint pair, packets that are sent by the client appear at service end, and packets that are sent by the service end up on the client side. Packets can be retrieved in order from the receive queue in the PipeEndpoint. The type of the packets is defined by the member definition. An endpoint can request a Packet Acknowledgment to be sent once the packet is received by setting RequestPacketAck to true. SendPacket is used to send packets, and ReceivePacket is used to receive the next packet in the queue. Available can be used to determine is more packets are available to receive. Pipe endpoint pairs are created with the Connect function on the client. Either the client or the service can close the endpoint pair using the Close function. A pipe is specified by the keyword pipe followed by the packet type, followed by the member name of the pipe. An example:

pipe double[] sensordata

Pipes can use modifiers readonly, writeonly, and unreliable. See modifiers. Pipes are normally reliable, with packets guaranteed to arrive in order without loss, as long as the transport connection is active. Pipes marked unreliable do not guarantee delivery or order of packets. Pipes marked readonly are often used with PipeBroadcaster to send packets to all connected clients.

Callbacks

Keyword: callback

Callbacks are essentially reverse functions, meaning that they allow a service to call a function on a client. Because a service can have multiple clients connected, the service must specify which client to call. The syntax is equivalent to the function, just replace function with callback. An example:

callback double addTwoNumbersOnClient(int32 a, double b)

Callbacks do not support generators.

Wires

Keyword: wire

Wires are very similar to pipes, however rather than providing a stream of packets the wire is used when only the "most recent" value is of interest. It is similar in concept to a "port" in Simulink. Wires may be transmitted over lossy channels or channels with latency where packets may not arrive or may arrive out of order. In these situations the lost or out of order packet will be discarded and only the newest value will be used. Each packet has a timestamp of when it is sent (from the sender's clock). Wires are full duplex like pipes meaning it has two-way communication, but unlike pipes they are not indexed so there is only one connection per client object reference. The wire allows for a WireConnection pair to be created with one WireConnection on the client and the other WireConnection on the service. Unlike pipes, each wire member can only create one connection pair per client, per service object instance. The WireConnection is used by setting the OutValue to the current value. This sends the new value to the opposite WireConnection, which updates its InValue. The same can be reversed. For instance, setting the OutValue on the service changes the InValue on the client, and setting the OutValue on the client changes the InValue on the service. It as also possible to receive the LastValueReceivedTime and LastValueSentTime to read the timestamps on the values. Note that LastValueReceivedTime is in the sender's clock, not the local clock and is generated when it is first transmitted. Either the client or the service can close the WireConnection pair using the Close function.

When a WireConnection pair is created by the client, the most recent values begin streaming between the pair. Sometimes the client needs to read the InValue or set the OutValue, but does not require a streaming update. The Wire provides PeekInValue, PeekOutValue, and PokeOutValue for this purpose. These functions use request-response instead of streaming the most recent values.

An example wire member definition:

wire double[2] currentposition

Wires can use modifiers readonly or writeonly. See modifiers.

Memories

Keyword: memory

Memories represent a random-access segment of numeric primitive arrays, numeric primitive multi-dim arrays, pod arrays, pod multi-dim arrays, namedarrays arrays, and namedarrays multi-dim arrays. The memory member is available for two reasons: it will break down large read and writes into smaller calls to prevent buffer overruns (most transports limit message sizes to 10 MB) and the memory also provides the basis for future shared-memory segments. An example:

memory double[] datahistory

Memories can use modifiers readonly or writeonly. See modifiers.

Constants

Constants can be specified using the \texttt{constant} keyword. The constants can be numbers, single dimensional arrays, or strings. Constants can be declared either in the global service definition scope or in objects.

constant uint32 myconst 0xFB
constant double[] myarray {10.3, 584.9, 594}
constant string mystring "Hello world!"

Exceptions

Robot Raconteur will transparently pass exceptions thrown by the receiver to the caller for transactions such as functions, properties, callbacks, and memory reads/writes. Normally these exceptions are of the type RobotRaconteurRemoteException which is a universal container for all types of exceptions. In some cases it is useful to have named exceptions that can be passed from receiver to caller and keep their class type. These custom exceptions inherit from RobotRaconteurRemoteException. Service definitions can define these exceptions. Exceptions are defined by starting the line with exception followed by the name of the exception. For example, the following line will define the exception "MyException" which can then be used in any of the supported languages:

exception MyException

Using

To reduce the clutter in a service definition file, the using statement can be used to alias an imported type.

using example.importeddef.obj1

as can be used to change the name locally.

using exmaple.importeddef.obj1 as another_obj1

Conventions

Some conventions are recommended for service definition formatting:

  • Service names should use Java style package names, using reverse domain name order. All letters should be lowercase. experimental or communication can be used for the TLD if no domain is available.
  • Enumeration, structure, pod, namedarray, and object names should be nouns with each internal word capitalized (UpperCamelCase)
  • All letters in constant names should be capitalized with internal words separated with underscores (ALL_CAPS)
  • Field names, member names, parameter names, modifier names, and enumeration values should use lowercase for letters, and separate each internal word with underscores (snake_case)
  • Service definition scope declaration should not be indented. Fields, members, and enum values should be indented four spaces. Tabs should not be used for indentation.
  • It is suggested that lines be split after 79 characters using the line continuation character \
  • Line continuations should be indented four spaces more than the line before the continuation. Additional line continuations should match the indentation of the first continuation.
  • Comments should match the indentation of the relevant declaration