Service Definition

ServiceDefinition Example experimental.create2 (below) 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 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 local to the client or service.

#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. “Namespaces” 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, “experimental” 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 “experimental.create2” 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.)

Value types

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

Primitives

Primitives consist of scalar numbers, single dimensional number arrays, and strings. Table 1 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 functions that do not have a return value.

Robot Raconteur Primitives

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. Example “experimental.create2” 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 always 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 types with fixed or maximum size 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 (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 data is a value type (but not another map). 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 keying.
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.
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, and lists can be null. All other types are not 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.

Properties (Keyword: property)

Properties are similar to class variables (field). They can be written to (set) or read from (get). A property can take on any 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 must be unique). An example:

property double myvar

Properties can use modifiers readonly, writeonly, urgent, and/or perclient. See Member 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 See Member 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. 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 Member 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 the 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 Member Modifiers.

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)

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 ignored 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, per connection. The wire allows for a “WireConnection” pair to be created with one “WireConnection” on the client and the other “WireConnection” on the service. 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. As of Robot Raconteur version 0.12, wires also have “lifespan” for “InValue” and “OutValue”, which will invalidate the received data after a specified time after reception. This prevents the wire from returning stale data. Use the “InValueLifespan” and “OutValueLifespan” to configure.

The wire provides the basis for real-time communication. (See also Time-critical software with Wire member.) An example wire member definition:

wire double[2] currentposition

Wires can use modifiers readonly or writeonly. See Member 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, unless “jumbo” messages are enabled) 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 Member Modifiers.

Constants

Constants can be specified using the constant keyword. The constants can be numbers, single dimensional arrays, or strings. Constants can exist either in the global service definition scope, in objects, or in structs.

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

Robot Raconteur naming

When naming things like service definitions, objects, structures, and members, certain rules must be followed. The name must consist of letters, numbers, and underscores (_). Names must start with a letter and may not start with any uppercase/lowercase combination of “RobotRaconteur”, “RR”, “get_”, “set_”, or “_async_”. Service names may not end with “_signed”.