Getting Started With PSchema In C

This tutorial will take you through all the stages involved in creating a simple learning agent that can interact with a small simulated world that we’ve provided. We assume some familiarity with C programming and the GLib framework, but all PSchema related concepts will be introduced from scratch.

The sandbox

PSchema Sandbox

Figure 1: The PSchema tutorial sandbox in its initial state.

To make this tutorial more interactive we have created a simple application called the “Sandbox”. This is an interactive world consisting of three buttons which have different effects on the environment. Inhabiting this world is our learning agent, a disembodied hand capable of moving around the world and manipulating objects.

The application is distributed with PSchema in the doc/tutorial/sandbox/ directory. Simply move to that directory and execute ./sandbox.py to start the application.

The agent in the sandbox communicates with us via a network socket, so can be controlled from any programming language, in this example we’ll be using C. However, before we start writing a controller for the agent we can get a feel for its commands and behaviour by directing it ourselves.

By default the sandbox listens on port 6000 for connections, first we connect to it via telnet:

$ telnet localhost 6000
Trying 127.0.0.1…
Connected to localhost.
Escape character is ‘^]’.

First lets ask it what can it currently see in the world, by sending the command:

GET_SENSORS

It then responds with a list of observations about the world:

POSITION 150, OBJECT ‘Hand’
POSITION 190, OBJECT ‘Blue Button’
POSITION 192, OBJECT ‘Green Button’
POSITION 188, OBJECT ‘Red Button’

Figure 2. The world is divided into a grid of 32x32 pixel squares, which are sequentially numbered.

The sandbox’s positioning system is handled via sequentially numbered grid squares (each 32×32 pixels) as illustrated in figure 2.

We can tell our hand to move to a new location by issuing the command:

MOVE 32

From the GET_SENSORS command that we issued earlier we know that there’s a blue button at position 190. If we move to that location and request the sensor information again:

MOVE 190
GET_SENSORS

We receive some new sensor information in the response, relating to the fact that we’re now touching the blue button:

POSITION 190, OBJECT ‘Hand’
POSITION 190, OBJECT ‘Blue Button’
TOUCHING ‘Blue Button’
POSITION 192, OBJECT ‘Green Button’
POSITION 188, OBJECT ‘Red Button’

There are two further commands available to us when controlling the hand, GRASP and RELEASE. These can be used for manipulating objects, some objects in the world can be picked up while others cannot. While it’s not possible to actually pick up the buttons, when we attempt to grasp them we instead press them causing different types of food to be dispensed into the world depending on which button was pressed. As before we can test this functionality by first sending a command to move the hand over the red button (position 188), then sending a grasp command.

MOVE 188
GRASP
GET_SENSORS

Pressing buttons in the sandbox

Figure 3. Upon pressing the red button a strawberry appears in the world.

And in the response we can now see a strawberry positioned above the button we just pressed. This can be seen visually in figure 3.

POSITION 192, OBJECT ‘Green Button’
POSITION 148, OBJECT ‘Strawberry’
POSITION 190, OBJECT ‘Blue Button’
POSITION 188, OBJECT ‘Red Button’
TOUCHING ‘Red Button’
POSITION 188, OBJECT ‘Hand’

In contrast to buttons, items of food can be picked up when grasped by the hand. Once held objects will move with the hand until a release command is issued. First we grasp the strawberry:

MOVE 148
GRASP
GET_SENSORS

And find that upon doing so we receive some new sensor feedback informing us that we’re now holding the strawberry:

POSITION 192, OBJECT ‘Green Button’
POSITION 148, OBJECT ‘Strawberry’
POSITION 190, OBJECT ‘Blue Button’
POSITION 188, OBJECT ‘Red Button’
POSITION 148, OBJECT ‘Hand’
HOLDING ‘Strawberry’

Finally we can move the hand, still holding the strawberry to a new location, release the strawberry and move our hand away:

MOVE 133
RELEASE
MOVE 150

At this point we know roughly how the simulated world works ourselves and what actions are available to our agent, next we can begin writing our control software so that our agent can start to act independently of us and learn about the world for itself.

Creating an application that uses PSchema

To start off with we create a very simple skeleton of an application that initialises the PSchema library and sets up a GTK window which we’ll use later for showing a few buttons. Because GTK and PSchema are both based around GLib they can share the same main loop, however it is not necessary to make use of GTK when writing the control software, instead a simple GLib main loop can be created without the need for GTK. Later on we’ll also be making use of GIO to communicate with the simulator over a network connection.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <pschema.h>
#include <gtk/gtk.h>
#include <gio/gio.h>
 
PSchema *ps;
GtkWidget *win;
 
int main(int argc, char **argv) {
 
        // Setup GTK
        gtk_init(&argc, &argv);
 
        // Create a window
        win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
        // End the GTK main loop when our window gets destroyed
        g_signal_connect(win, "destroy", G_CALLBACK(gtk_main_quit), NULL);
        // Display our window
        gtk_widget_show_all(win);
 
        // Initialise PSchema
        ps = pschema_new();
 
        // Start the GTK main loop
        gtk_main();
 
}

Makefile

If installed correctly PSchema informs pkg-config what flags and libraries are required when compiling and linking, so the Makefile for our simple controller application is relatively trivial:

1
2
3
4
5
6
7
8
9
CFLAGS=-g `pkg-config --cflags gtk+-2.0 pschema`
LDLIBS=`pkg-config --libs gtk+-2.0 pschema`
 
controller: controller.o
 
all: controller
 
clean:
        rm controller *.o

To build our new control software simply run make. The controller can then be executed by running ./controller. For now it doesn’t do much besides initialising the frameworks and showing an empty window, but this should allow you to ensure that it is compiling correctly and linking against the PSchema libraries without any issues.

Debugging

In addition to using a full-fledged debugger such as GDB for inspecting the inner workings of the framework it’s possible to request varying levels of debug information on different topics through a couple of environment variables:

  • PSCHEMA_DEBUG – This sets the amount of debugging information required, taken as an integer between 1 and 5. When set to 1 it will give very few high level messages, when set to 5 it will give very detailed information.
  • PSCHEMA_DEBUG_FILTER – This specifies which subsystem debugging information should be output for. If unspecified then all debugging output information will be displayed. For example it can be set to “excitation” for debugging messages relating directly to the excitation calculations.

We can see this in action with our own application:

$ PSCHEMA_DEBUG=5 ./controller
1326745281.562089 [1 - general] Debugging output enabled (level 5).
1326745281.562185 [1 - general] Showing all debug messages.

Communicating with the simulator

Now that we have all the basics set up we can start interacting with the simulator. In this example we communicate with our simulated agent through a simple TCP socket. This could easily be replaced with a custom agent control library, or robotics middleware such as Player or YARP, to allow for communication with robots in the real world or agents in other virtual environments.

First we add definitions for a function to handle incoming network data, a function to move the hand, a function to request sensor data, and a pointer for our socket communication channel.

5
6
7
8
9
10
11
12
13
PSchema *ps;
GtkWidget *win;
GIOChannel *channel;
 
static gboolean data_received(GIOChannel *channel, GIOCondition cond, gpointer data);
void move_hand(int position);
void get_sensors();
 
int main(int argc, char **argv) {

Then during our general initialisation code we can set up our connection to the simulator.

25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
        // Initialise PSchema
        ps = pschema_new();
 
        // Create our network connection to the simulator
        GSocketClient *socket_client = g_socket_client_new();
        GError *error = NULL;
        GSocketConnection *socket_connection = g_socket_client_connect_to_host(socket_client,
                        "localhost", 6000, NULL, &error);
        if (error != NULL) {
                printf("%s\n", error->message);
                return EXIT_FAILURE;
        }
        GSocket *socket = g_socket_connection_get_socket(socket_connection);
        channel = g_io_channel_unix_new(g_socket_get_fd(socket));
        // Call our data_received() function whenever new network messages come in
        g_io_add_watch(channel, G_IO_IN | G_IO_ERR | G_IO_HUP, data_received, NULL);
 
        // Start the GTK main loop
        gtk_main();
 
}

Next we can add a skeleton implementation of our data_received() function:

47
48
49
50
51
52
53
54
55
56
static gboolean data_received(GIOChannel *channel, GIOCondition cond, gpointer data) {
        GError *error = NULL;
        GString *line = g_string_new("");
        g_io_channel_read_line_string(channel, line, NULL, &error);
        if (error != NULL) {
                printf("%s\n", error->message);
                gtk_main_quit();
        }
        printf("%s", line->str);
}

For now this function just prints out any data it receives from the simulator, later on we’ll modify this so it can create a representation of the world state out of the sensor values sent to it from the simulator.

Now that we can receive messages from the simulator we can try sending some commands. First we’ll implement our move_hand() function:

58
59
60
61
62
63
64
65
66
void move_hand(int position) {
        gsize bytes_written; 
        // Construct a movement command
        GString *command = g_string_new("");
        g_string_printf(command, "MOVE %d\n", position);
        // Send it to the simulator
        g_io_channel_write_chars(channel, command->str, command->len, &bytes_written, NULL);
        g_io_channel_flush(channel, NULL);
}

Finally we need to implement our function for requesting sensor data. This’ll be very similar to the move_hand() function, as it’s just sending the GET_SENSORS message over the network. All actually processing of the sensor information that the simulator returns will be handled in the data_received() function.

68
69
70
71
72
73
74
void get_sensors() {
        gsize bytes_written;
        // Send a GET_SENSORS request to the simulator (data_received will process the response)
        GString *command = g_string_new("GET_SENSORS");
        g_io_channel_write_chars(channel, command->str, command->len, &bytes_written, NULL);
        g_io_channel_flush(channel, NULL);
}

To test our new functions we can try calling them immediately before starting the GTK main loop:

39
40
41
42
43
44
45
46
        // Call our data_received() function whenever new network messages come in
        g_io_add_watch(channel, G_IO_IN | G_IO_ERR | G_IO_HUP, data_received, NULL);
 
        move_hand(123);
        get_sensors();
 
        // Start the GTK main loop
        gtk_main();

This should move the hand to position 123 then request the sensor data. When the simulator responds to the sensor request this will be processed by our data_received() function, which for now will simply print the information to stdout:

$ ./controller
POSITION 192, OBJECT ‘Green Button’
POSITION 190, OBJECT ‘Blue Button’
POSITION 188, OBJECT ‘Red Button’
POSITION 123, OBJECT ‘Hand’

Observations

Now that we’re receiving sensor information from the simulator we need to convert it into a form that the PSchema framework can reason about. We do this by first creating some custom observation classes, inheriting from the abstract Observation class defined by PSchema. While there are already a few basic observation types built into the framework for this tutorial we will create our own custom class to more precisely represent the information we’re being supplied with.

There is a certain amount of boiler plate code involved in defining a new GLib based class, it’s not necessary to understand this in detail, instead the main focus will be placed on the aspects relating specifically to PSchema observations.

Since the sensor information we’re receiving at the moment is about the positions of objects we’ll create a new observation class called MyObjectObservation which can store this information. To begin with we need to create a header file, my_object_observation.h, to store the declaration of our new class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <pschema.h>
 
#define TYPE_MY_OBJECT_OBSERVATION (my_object_observation_get_type ())
#define MY_OBJECT_OBSERVATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
                        TYPE_MY_OBJECT_OBSERVATION, MyObjectObservation))
#define MY_OBJECT_OBSERVATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), \
                        TYPE_MY_OBJECT_OBSERVATION, MyObjectObservationClass))
#define IS_MY_OBJECT_OBSERVATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
                        TYPE_MY_OBJECT_OBSERVATION))
#define IS_MY_OBJECT_OBSERVATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \
                        TYPE_MY_OBJECT_OBSERVATION))
#define MY_OBJECT_OBSERVATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), \
                        TYPE_MY_OBJECT_OBSERVATION, MyObjectObservationClass))
 
typedef struct _MyObjectObservation MyObjectObservation;
typedef struct _MyObjectObservationClass MyObjectObservationClass;
 
struct _MyObjectObservation {
        Observation parent_instance;
        // The properties we want to store about this observation
        gint position;
        gchar *name;
        // Used for generalisation
        gchar *position_var;
        gchar *name_var;
};
 
struct _MyObjectObservationClass {
        ObservationClass parent_class;
};
 
static gpointer my_object_observation_parent_class = NULL;
GType my_object_observation_get_type() G_GNUC_CONST;
static gboolean my_object_observation_equals(Observation* base, Observation* o2);
static void my_object_observation_parse_node(Observation* base, xmlNode* node);
static Observation* my_object_observation_copy(Observation* base);
MyObjectObservation* my_object_observation_new();
MyObjectObservation* my_object_observation_construct(GType my_object_type);
static GeeHashMap* my_object_observation_get_properties(Observation* base);
static void my_object_observation_set_property_var(Observation* base,
                const gchar* property, const gchar* variable);
static void my_object_observation_set_concrete_var(Observation* base,
                const gchar* property, const gchar* variable);
static void my_object_observation_finalize(GObject* obj);

Then we can create a second file called my_object_observation.c and start implementing the relevant methods. We’ll begin by writing our initialisation functions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include "my_object_observation.h"
 
 
static void my_object_observation_class_init(MyObjectObservationClass * klass) {
        // Find out parent class
        my_object_observation_parent_class = g_type_class_peek_parent(klass);
        // Set which methods to use for our class methods
        // Overrides from the Observation class
        OBSERVATION_CLASS(klass)->equals = my_object_observation_equals;
        OBSERVATION_CLASS(klass)->parse_node = my_object_observation_parse_node;
        OBSERVATION_CLASS(klass)->copy = my_object_observation_copy;
        OBSERVATION_CLASS(klass)->get_properties = my_object_observation_get_properties;
        OBSERVATION_CLASS(klass)->set_property_var = my_object_observation_set_property_var;
        OBSERVATION_CLASS(klass)->set_concrete_var = my_object_observation_set_concrete_var;
        // Overrides from the GObject class
        G_OBJECT_CLASS(klass)->finalize = my_object_observation_finalize;
}
 
 
static void my_object_observation_instance_init(MyObjectObservation *self) {
        self->name = NULL;
        self->name_var = NULL;
        self->position_var = NULL;
}
 
 
MyObjectObservation* my_object_observation_construct(GType object_type) {
        return (MyObjectObservation *) observation_construct(object_type);
}
 
 
MyObjectObservation* my_object_observation_new(void) {
        return my_object_observation_construct(TYPE_MY_OBJECT_OBSERVATION);
}
 
 
GType my_object_observation_get_type(void) {
        static volatile gsize my_object_observation_type_id_volatile = 0;
        if (g_once_init_enter(&my_object_observation_type_id_volatile)) {
                static const GTypeInfo g_define_type_info = {
                        sizeof (VisualObservationClass),
                        (GBaseInitFunc) NULL,
                        (GBaseFinalizeFunc) NULL,
                        (GClassInitFunc) my_object_observation_class_init,
                        (GClassFinalizeFunc) NULL,
                        NULL,
                        sizeof (MyObjectObservation),
                        0, 
                        (GInstanceInitFunc) my_object_observation_instance_init,
                        NULL
                };
                GType my_object_observation_type_id = g_type_register_static(TYPE_OBSERVATION,
                        "MyObjectObservation", &g_define_type_info, 0);
                g_once_init_leave(&my_object_observation_type_id_volatile, 
                                my_object_observation_type_id);
        }
        return my_object_observation_type_id_volatile;
}

It’s now necessary to modify our Makefile slightly so that our new observation class gets compiled and linked to our controller:

4
controller: controller.o my_object_observation.o

However we still need to finish writing our observation class before we can make use of it.

Next we’ll write simple method for checking to see if two instances of MyObjectObservation are equal:

61
62
63
64
65
66
67
68
69
70
static gboolean my_object_observation_equals(Observation *base, Observation *o2) {
        MyObjectObservation *self = MY_OBJECT_OBSERVATION(base);
        if (G_TYPE_FROM_INSTANCE(G_OBJECT(self)) != G_TYPE_FROM_INSTANCE(G_OBJECT(o2))) {
                // Observations are not of the same time, so can't be equal
                return FALSE;
        }
        MyObjectObservation *oo2 = MY_OBJECT_OBSERVATION(o2);
        // Check to see if properties are the same
        return (self->position == oo2->position) && (strcmp(self->name, oo2->name) == 0);
}

When an observation is loaded from a previously saved XML file its XML node is passed to the relevant class so that any properties specific to that observation class can be parsed. Any common properties (such as the number of times an observation has been seen, or the sensor id of that property) can be handled by the observation base class. So for our implementation we simple need to look for the “position” and “name” properties and read them in to the corresponding variables:

73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
static void my_object_observation_parse_node(Observation *base, xmlNode *node) {
        MyObjectObservation *self = MY_OBJECT_OBSERVATION(base);
        // Let the Observation class handle parsing of properties common to all observations
        OBSERVATION_CLASS(my_object_observation_parent_class)->parse_node(base, node);
        xmlAttr *prop;
        for (prop = node->properties; prop != NULL; prop = prop->next) {
                char *val = prop->children->content;
                if (strcmp(prop->name, "position") == 0) {
                        if (string_get(val, 0) == '$') {
                                // This is a generalised property
                                self->position_var = val;
                        } else {
                                // This is a concrete property
                                self->position = atoi(val);
                        }
                } else if (strcmp(prop->name, "name") == 0) {
                        if (string_get(val, 0) == '$') {
                                // Generalised
                                self->name_var = val;
                        } else {
                                // Concrete
                                self->name = val;
                        }
                }
        }
}

Next a function for creating a copy of the current observation:

101
102
103
104
105
106
107
108
109
110
111
112
113
static Observation* my_object_observation_copy(Observation *base) {
        MyObjectObservation *self = MY_OBJECT_OBSERVATION(base);
        // Create a new instances of MyObjectObservation
        MyObjectObservation *copy = my_object_observation_new();
        // Populate it with the same values that the current instance has
        int sensor_id = observation_get_sensor_id(base);
        copy->name = strdup(self->name);
        copy->name_var = strdup(self->name_var);
        copy->position = self->position;
        copy->position_var = strdup(self->position_var);
        observation_set_sensor_id(OBSERVATION(copy), sensor_id);
        return OBSERVATION(copy);
}

Now a function for retrieving all the properties of our observation, either in generalised or concrete form:

116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
static GeeHashMap* my_object_observation_get_properties(Observation *base) {
        MyObjectObservation *self = MY_OBJECT_OBSERVATION(base);
        // We return our properties in a hashmap of strings
        GeeHashMap *properties = gee_hash_map_new(G_TYPE_STRING,
                        (GBoxedCopyFunc) g_strdup,
                        g_free,
                        G_TYPE_STRING,
                        (GBoxedCopyFunc) g_strdup,
                        g_free,
                        NULL,
                        NULL,
                        NULL);
        if (self->name_var != NULL) {
                // Abstract property
                gee_abstract_map_set(GEE_ABSTRACT_MAP(properties), "name",
                                self->name_var);
        } else {
                // Concrete property
                gee_abstract_map_set(GEE_ABSTRACT_MAP(properties), "name",
                                self->name);
        }
 
        if (self->position_var != NULL) {
                gee_abstract_map_set(GEE_ABSTRACT_MAP(properties), "position",
                                self->position_var); 
        } else {
                gee_abstract_map_set(GEE_ABSTRACT_MAP(properties), "position",
                                g_strdup_printf("%i", self->position));
        }
 
        return properties;
}

Next a function for setting a generalised property, this is made use of by the generaliser to assign variables to properties:

150
151
152
153
154
155
156
157
158
static void my_object_observation_set_property_var(Observation *base, const gchar *property,
               const gchar *variable) {
        MyObjectObservation *self = MY_OBJECT_OBSERVATION(base);
        if (strcmp(property, "name") == 0) {
                self->name_var = g_strdup(variable);
        } else if (strcmp(property, "position") == 0) {
                self->position_var = g_strdup(variable);
        }
}

Along with that we need a corresponding function to populate a generalised property with a concrete value:

161
162
163
164
165
166
167
168
169
static void my_object_observation_set_concrete_var(Observation *base, const gchar *property,
                const gchar *variable) {
        MyObjectObservation *self = MY_OBJECT_OBSERVATION(base);
        if (strcmp(property, "name") == 0) {
                self->name = variable;
        } else if (strcmp(property, "position") == 0) {
                self->position = atoi(variable);
        }
}

Finally we can add a finalize method for freeing the memory used by our observation when it is no longer needed:

172
173
174
175
176
177
178
179
180
static void my_object_observation_finalize(GObject *obj) {
        MyObjectObservation *self = MY_OBJECT_OBSERVATION(obj);
        // Free our strings
        g_free(self->name);
        g_free(self->name_var);
        g_free(self->position_var);
        // Let the Observation base class perform any common finalization
        G_OBJECT_CLASS(my_object_observation_parent_class)->finalize(obj);
}

This completes the MyObjectObservation class, however to fully represent the information we get back about the world we’ll need two other Observation types, MyTouchObservation and MyHoldingObservation. Using MyObjectObservation as an example try writing these two Observations. MyTouchObservation should have one boolean property “touching” and MyHoldingObservation should have one string property “object”. If you have difficulty implementing these you can find a reference implementation in the doc/tutorial/controller/ directory. Don’t forget to modify your Makefile appropriately to compile these new observations into your controller.

Actions

Using the Observation classes we wrote earlier we’re now able to represent everything our sensors tell us about the world. However we’re not yet able to represent the actions our agent itself takes, to do this we need some Action classes. This should be very familiar to you after creating the Observation classes, so we’ll simply provide the full source for the MyMovementAction class here.

First my_movement_action.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <pschema.h>
 
#define TYPE_MY_MOVEMENT_ACTION (my_movement_action_get_type ())
#define MY_MOVEMENT_ACTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
                        TYPE_MY_MOVEMENT_ACTION, MyMovementAction))
#define MY_MOVEMENT_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), \
                        TYPE_MY_MOVEMENT_ACTION, MyMovementActionClass))
#define IS_MY_MOVEMENT_ACTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
                        TYPE_MY_MOVEMENT_ACTION))
#define IS_MY_MOVEMENT_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \
                        TYPE_MY_MOVEMENT_ACTION))
#define MY_MOVEMENT_ACTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), \
                        TYPE_MY_MOVEMENT_ACTION, MyMovementActionClass))
 
typedef struct _MyMovementAction MyMovementAction;
typedef struct _MyMovementActionClass MyMovementActionClass;
 
struct _MyMovementAction {
        Action parent_instance;
        gint _joint;
        gdouble _angle;
};
 
struct _MyMovementActionClass {
        ActionClass parent_class;
};
 
static gpointer my_movement_action_parent_class = NULL;
GType my_movement_action_get_type() G_GNUC_CONST;
static gchar* my_movement_action_to_xml(Action *base);
static gboolean my_movement_action_equals(Action *base, Action *o2);
static void my_movement_action_parse_node(Action *base, xmlNode *node);
static void my_movement_action_real_execute(Action *base);
static Action* my_movement_action_copy(Action *base);
MyMovementAction* my_movement_action_new();
MyMovementAction* my_movement_action_construct(GType my_object_type);
static void my_movement_action_finalize(GObject* obj);

Then my_movement_action.c:

1
 

The only main difference from the Observation classes that deserves additional extra attention is the introduction of signals. Rather than directly implementing the code used to make our agent carry out these action within our Action class we send a signal back to the controller software. This means that our action types can be portable across many different agents, all with different controllers. We’ll look at this in more detail when we connect to the signal to the move() function within our main controller.c file.

It will now be necessary for you to create a couple of additional Action classes based on this; MyGraspAction and MyReleaseAction. Again if you have difficulties there are reference implementations in doc/tutorial/controller/.

Bootstrapping

 

Saving

 

Loading

 

Source code

The final version of the controller developed throughout this tutorial can be found in the doc/tutorial/controller/ directory, however it is recommended that you construct the controller yourself whilst following the tutorial so that each concept can be studied in isolation and then built upon.