Usage

Include the header file to use the device library.

#include <simpleRPC.h>

The library provides the interface() function, which is responsible for all serial communication with the host. To use this function, first enable serial communication at 9600 baud in the setup() body.

void setup(void) {
  Serial.begin(9600);
}

Standard methods

Methods are exported by calling the interface() function from the loop() body. This function accepts (function, documentation) pairs as parameters.

Interface function parameters.
parameter description
0 Method one.
1 Documentation string of method one.
2 Method two.
3 Documentation string of method two.

A documentation string consists of a list of key-value pairs in the form key: value delimited by the @ character. The first pair in this list is reserved for the method name and its description, all subsequent pairs are used to name and describe parameters or to describe a return value.

Documentation string.
field prefix key value
  Method name. Method description.
@ Parameter name. Parameter description.
@ return Return value description.

The documentation string may be incomplete or empty. The following defaults are used for missing keys. All descriptions may be empty.

Default names.
key default
Method name. method followed by a number, e.g., method2.
Parameter name. arg followed by a number, e.g., arg0.
return return

To reduce the memory footprint, the use of the F() macro is allowed in the interface() function. This stores the documentation string in program memory instead of SRAM. For more information, see the progmem documentation.

Example

Suppose we want to export a method that sets the brightness of an LED and a method that takes one parameter and returns a value.

void setLed(byte brightness) {
  analogWrite(13, brightness);
}

int inc(int a) {
  return a + 1;
}

Exporting these methods in the loop() body looks as follows:

void loop(void) {
  interface(
    inc, "inc: Increment a value. @a: Value. @return: a + 1.",
    setLed, "set_led: Set LED brightness. @brightness: Brightness.");
}

We can now build and upload the sketch.

The client reference documentation includes an example on how these methods can be accessed from the host.

Class methods

Class member functions are different from ordinary functions in the sense that they always operate on an object. This is why it is not possible to simply pass a function pointer, but to also provide a class instance for the function to operate on. To facilitate this, the pack() function can be used to combine a class instance and a function pointer before passing them to interface().

For a class instance c of class C, the class member function f() can be packed as follows:

pack(&c, &C::f)

The result can be passed to interface().

Example

Suppose we have a library named led which provides the class LED. This class has a member function named setBrightness.

#include "led.h"

LED led(13);

Exporting this class method as a remote call goes as follows:

void loop(void) {
  interface(
    pack(&led, &LED::setBrightness),
      "set_led: Set LED brightness. @brightness: Brightness.");
}

Tuples

Tuples can be used to group multiple objects of different types together. A Tuple has two members, head and tail, where head is of any type, and tail is an other Tuple.

Tuples can be initialised with a brace-initializer-list as follows.

Tuple<int, char> t = {10, 'c'};

Elements of a Tuple can be retrieved in two ways, either via its head and tail member variables, or with the get<>() helper function.

int i = t.head;
char c = t.tail.head;

int j = get<0>(t);
char d = get<1>(t)';

Likewise, assignment of an element can be done via its member variables or with the get<>() helper function.

t.head = 11;
t.tail.head = 'd';

get<0>(t) = 11;
get<1>(t) = 'd';

There are two additional helper functions available for Tuples: pack() and castStruct(). pack() can be used to create a temporary tuple to be used in a function call.

function(pack('a', 'b', 10));

Likewise, the castStruct() function can be used to convert a C struct to a Tuple.

struct S {
  int i;
  char c;
};

S s;
function(castStruct<int, char>(s));

Note that a Tuple, like any higher order data structure should be passed by reference.

Objects

Objects behave much like Tuples, but they are serialised differently (see the Protocol section).

Objects can be initialised via a constructor as follows.

Object<int, char> o(10, 'c');

Element retrieval and assignment is identical to that of Tuples.

Note that an Object, like any higher order data structure should be passed by reference.

Vectors

A Vector is a sequence container that implements storage of data elements. The type of the vector is given at initialisation time via a template parameter, e.g., int.

Vector<int> v;
Vector<int> u(12);

In this example, Vector v is of size 0 and u is of size 12. A Vector can also be initialised with a pointer to an allocated block of memory.

Vector<int> v(12, data);

The memory block is freed when the Vector is destroyed. If this is not desirable, an additional flag destroy can be passed to the constructor as follows.

Vector<int> v(12, data, false);

This behaviour can also be changed by manipulating the destroy member variable.

A Vector can be resized using the resize method.

v.resize(20);

The size member variable contains the current size of the Vector.

Element retrieval and assignment is done in the usual way.

int i = v[10];

v[11] = 9;

Note that a Vector, like any higher order data structure should be passed by reference.

Complex objects

Arbitrary combinations of Tuples, Objects and Vectors can be made to construct complex objects.

In the following example, we create a 2-dimensional matrix of integers, a Vector of Tuples and an Object containing an integer, a Vector and an other Object respectively.

Vector<Vector<int> > matrix;

Vector<Tuple<int, char> > v;

Object<int, Vector<int>, Object<char, long> > o;