Service Definition
ServiceDefinition Example experimental.create3
(below)
shows the code contained in the “experimental.create3.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
#This example is for the original iRobot Create using the serial Open Interface (OI) protocol
service experimental.create3
stdver 0.10
enum CreateStateFlags
unknown = 0,
bump_right = 0x1,
bump_left = 0x2,
wheel_drop_right = 0x4,
wheel_drop_left = 0x8,
wheel_drop_caster = 0x10,
wall_sensor = 0x20,
cliff_left = 0x40,
cliff_front_left = 0x80,
cliff_front_right = 0x100,
cliff_right = 0x200,
virtual_wall = 0x400,
play_button = 0x800,
advance_button = 0x1000,
error = 0x800000
end
struct CreateState
field double time
field uint32 create_state_flags
field double velocity
field double radius
field double right_wheel_velocity
field double left_wheel_velocity
field double distance_traveled
field double angle_traveled
field double battery_charge
field double battery_capacity
end
object Create
constant double DRIVE_STRAIGHT 32.767
constant double SPIN_CLOCKWISE -1e-3
constant double SPIN_COUNTERCLOCKWISE 1e-3
function void drive(double velocity, double radius)
function void drive_direct(double right_wheel_velocity, double left_wheel_velocity)
function void stop()
function void setf_leds(bool play, bool advance)
property double distance_traveled [readonly]
property double angle_traveled [readonly]
property uint8 bumpers [readonly]
event bump()
wire CreateState create_state [readonly]
# Callback to be called when the play button is pressed
# claim_play_callback() will assign the current client as the target for the callback
# Practical implementations will likely want to use a more sophisticated mechanism to assign the callback
function void claim_play_callback()
callback uint8[] play_callback(double distance_traveled, double angle_traveled)
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.create3”. 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.create3” 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 exampleint32[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 exampleint32[100-]
can have up to 100 integers. Strings are always arrays so the brackets are not valid. Thevoid
type is only used for functions 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. Example “experimental.create3” shows the definition of the structure
CreateState
. A structure is started with the keywordstruct
followed by the structure name. It is ended with theend
keyword. The entries in the structure are defined with the keywordfield
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 theend
keyword. The entries in the pod are defined with the keywordfield
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 theend
keyword. The entries in the namedarray are defined with the keywordfield
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
orstring
. 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.CreateState{string}
andint32[]{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.CreateState{list}
andint32[]{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 theend
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/orperclient
. 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 keywordfunction
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 keywordobjref
followed by the object type followed by the objref member name. The object type can bevarobject
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
, andunreliable
. 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
orwriteonly
. 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
orwriteonly
. 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”.