In this section we describe the serial protocol.

Every exported method defined using the interface() function (see the usage_device section) is assigned a number between 0 and 254 in order of appearance. The number 0 maps to the first method, the number 1 maps to the second method, etc.

There are two types of calls to the device: the method discovery call and a remote procedure call. In both cases, communication is initiated by the host by writing one byte to the serial device.

Method discovery

Method discovery is initiated by the host by writing one byte with value 0xff to the serial device.

The device will respond with a header and a list of method descriptions delimited by an end of string signature (\0). The list is terminated by an additional end of string signature. The header format is given in the following table.

Header format.
size delimiter value description
  \0 simpleRPC Protocol identifier.
3   \3\0\0 (example) Protocol version (major, minor, patch).
1   < or > Endianness, < for little-endian, > for big-endian.
1   H (example) Type of size_t, needed for indexing vectors.

Each method description consists of a struct formatted function signature and a documentation string separated by a ;. The function signature starts with a struct formatted return type (if any), followed by a : and a space delimited list of struct formatted parameter types. The format of the documentation string is described in the Usage section.

For our example, the response for the method discovery request will look as follows.

h: h;inc: Increment a value. @a: Value. @return: a + 1.\0
: B;set_led: Set LED brightness. @brightness: Brightness.\0

For more complex objects, like Tuples, Objects and Vectors, some more syntax is needed to communicate their structure to the host.

A Tuple type is encoded as a compound type, e.g., hB (a 16-bit integer and a byte). It can be recognised by the absence of a space between the type signatures. Note that a concatenated or nested Tuple type can not be recognised from its signature, e.g., hB concatenated with ff is indistinguishable from hBff

An Object type is encoded as a compound type like a Tuple, but its type signature is enclosed in parentheses ( and ), which makes it possible to communicate its structure to the host, e.g., the concatenation of (hB) and (ff) is (hB)(ff) and the type signature of a nested Object may look like this ((hB)(ff)).

A Vector type signature is enclosed in brackets [ and ]. So a vector of 16-bit integers will have as type signature [h].

Finally, any arbitrary combination of Tuples, Objects and Vectors can be made, resulting in type signatures like [((hB)f)], i.e., a Vector of Objects that contain a Tuple of which the first element is an other Object (hB) and the second element is a float f.

Remote procedure calls

A remote procedure call is initiated by the host by writing one byte to the serial device of which the value maps to one of the exported methods (i.e., 0 maps to the first method, 1 to the second, etc.). If this method takes any parameters, their values are written to the serial device. After the parameter values have been received, the device executes the method and writes its return value (if any) back to the serial device.

All native C types (int, float, double, etc.), Tuples, Objects, Vectors and any combination of these are currently supported. The host is responsible for packing and unpacking of the values.

There is currently one built-in function.

Built-in functions.
index name description parameters returns
0 ping Echo a value. int data: Value. int: Value of data.


There does not seem to be any noticeable overhead of the library. We tested the throughput by calling the ping() function repeatedly at baud rates ranging from 600 baud to 115200 baud.

Throughput statistics.
baud rate calls per second
600 30
1200 60
2400 120
4800 239
9600 480
14400 718
19200 959
28800 1447
31250 1559
38400 1920
57600 2924
115200 5838

The number of calls per second scales linearly with the baud rate, even at the highest speed, there is no measurable overhead.