-
Notifications
You must be signed in to change notification settings - Fork 1
Module Tutorial
Most people will want to add modules to extend the code. This tutorial should go over the process in a hands-on way, which will do it's best to explain the code as it is written.
To begin, we need to create the files necessary for the module. In this tutorial, we will create a module that prints "Hello World" to the screen every time that its' frame is called. This module is going to be called "HelloWorld", and so, we first create two folders, both called helloworld
, one in the folder include/modules
and one in the folder src/modules
. We then create the "HelloWorldModule.h"
file in the include/modules/helloworld
directory, and the "HelloWorldModule.cpp"
file in the src/modules/helloworld
directory. These two files will form the base code for our module.
NOTE: Never, ever, ever make your header guards identical to your classname or filename. Trust me on this one :(
We now want to set up the header files. Open the "HelloWorldModule.h"
file we just created, and add the header guards. Your file should now look like:
/* HelloWorldModule header */
#ifndef _HelloWorldModule_H_
#define _HelloWorldModule_H_
#endif /*HelloWorldModule*/
The header guards prevent the module header file from being included more than once. We then add the base definition from the module class:
/* HelloWorldModule header */
#ifndef _HelloWorldModule_H_
#define _HelloWorldModule_H_
#include "include/common.h"
#include "include/modules/module.h"
#include "include/memory/intent.h"
#include "include/debug/debugging.h"
class HelloWorldModule : public Module {
public:
static HelloWorldModule* Instance();
bool RunFrame();
bool ProcessIntent(Intent &i);
bool Install();
bool Uninstall();
private:
static HelloWorldModule* instance;
};
#endif /*HelloWorldModule*/
In this step, we have done two major things. The first, is add the #include "include/common.h"
line. This includes all of the commmon headers from the main program including the defintion of Module, the interface class for our new module. The second step is to create te class itself. For this class, we need all of the methods that are defined in the module interface, which are our Reconfigure, RunFrame, ProcessIntent, Install and Uninstall functions. We also have to add the overhead for a singleton class. This isn't exactly necessary - but there's no reason we should have more than one HelloWorld module. Thus, we have the two static members for the singleton class overhead, and the five inherited methods.
Finally, we want to add the method that will print HelloWorld. Below is the finished HelloWorldModule.h file:
/* HelloWorldModule header */
#ifndef _HelloWorldModule_H_
#define _HelloWorldModule_H_
#include "include/common.h"
#include "include/modules/module.h"
#include "include/memory/intent.h"
#include "include/debug/debugging.h"
class HelloWorldModule : public Module {
public:
static HelloWorldModule* Instance();
bool RunFrame();
bool ProcessIntent(Intent &i);
bool Install();
bool Uninstall();
void PrintHelloWorld();
private:
static HelloWorldModule* instance;
};
#endif /*HelloWorldModule*/
Now open our "HelloWorldModule.cpp"
file in the src/modules/helloworld
folder. Let's start by including the HelloWorldModule header, and setting up the necessary code for singleton management:
#include "include/modules/helloworld/HelloWorldModule.h"
HelloWorldModule* HelloWorldModule::instance;
HelloWorldModule* HelloWorldModule::Instance() {
if (UNLIKELY(HelloWorldModule::instance == 0)) {
HelloWorldModule::instance = new HelloWorldModule();
}
return HelloWorldModule::instance;
}
Ok, let's look at what we added. We first initialize the instance pointer from the header, and then we create a method responsible for returning that pointer (unless the pointer is null, in which case it creates a new instance). One of the things to drawy your attention to is the use of the UNLIKELY macro. In GCC which is the supported compiler for this code, we replace the UNLIKELY with a branch prediction ABI call, which tells the compiler that the code is unlikely to execute this if statement. Thus, it is easy for the compiler to add branch prediction to the compiled machine code, which improves the performance of the application.
Now, let's add the rest of the functions from the header to our CPP file:
#include "include/modules/helloworld/HelloWorldModule.h"
HelloWorldModule* HelloWorldModule::instance;
HelloWorldModule* HelloWorldModule::Instance() {
if (UNLIKELY(HelloWorldModule::instance == 0)) {
HelloWorldModule::instance = new HelloWorldModule();
}
return HelloWorldModule::instance;
}
bool HelloWorldModule::RunFrame() {
PrintHelloWorld();
return true;
}
bool HelloWorldModule::ProcessIntent(Intent &i)
{
// Ignore it.
return true;
}
bool HelloWorldModule::Install() {
LOG_DEBUG << "Installed the 'Hello World' module";
return true;
}
bool HelloWorldModule::Uninstall() {
LOG_DEBUG << "Uninstalled the 'Hello World' module";
return true;
}
void HelloWorldModule::PrintHelloWorld() {
LOG_DEBUG << "Hello World!";
}
In this step, we use the LOG_DEBUG to print information in each of the callbacks. In each frame, we run the PrintHelloWorld()
method, like we wanted to in the goal of the tutorial. The only tricky function to understand is the Reconfigure method. In here, we open the .module configuration (more on this later), and read out the information using the luatables library from the core. This sets some of the configuration information for the module such as the name, required FPS and others. NOTE: In future versions, this method will be moved to the core, PLEASE DO NOT USE IT FOR INITIALIZATION. Instead, use the Install
function. We also ignore all intents. Our module does not recieve messages from other modules - for more information, and a tutorial on setting up intents, see the intent wiki page HERE.
Notice here that we don't override the virtual Reconfigure() method. While this is possible, I wouldn't do it unless I knew what I wanted to do. Try to use the Install() method instead.
Finally, we need to expose the module for dynamic loading. To do this, we add the following code to the top of the cpp under the include statement:
REGISTER_MODULE(HelloWorldModule)
What this does is exposes the Instance() function of our singleton class to the dynamic module loaded. Make sure that the argument passed to the macro is the Name of the Class which contains the module you have created. ** Future versions will have code support for dynamically loading void (*)(void) function references** This allows us to instantiate a single copy of our module, and access a pointer to that module.
This gives our final class cpp file:
#include "include/modules/helloworld/HelloWorldModule.h"
REGISTER_MODULE(HelloWorldModule)
HelloWorldModule* HelloWorldModule::instance;
HelloWorldModule* HelloWorldModule::Instance() {
if (UNLIKELY(HelloWorldModule::instance == 0)) {
HelloWorldModule::instance = new HelloWorldModule();
}
return HelloWorldModule::instance;
}
bool HelloWorldModule::RunFrame() {
PrintHelloWorld();
return true;
}
bool HelloWorldModule::ProcessIntent(Intent &i)
{
// Ignore it.
return true;
}
bool HelloWorldModule::Install() {
LOG_DEBUG << "Installed the 'Hello World' module";
return true;
}
bool HelloWorldModule::Uninstall() {
LOG_DEBUG << "Uninstalled the 'Hello World' module";
return true;
}
void HelloWorldModule::PrintHelloWorld() {
LOG_DEBUG << "Hello World!";
}
We're done with the hard coding! Now all we have to do is add the module to the build system and write a configuration file!
We now have to add the module to the build (that is, we create a target for the module in the CMAKE files). To do this, we open the src/modules/CMakeLists.txt
. At the end of the file, we append the following code:
if (${IS_CROSS} STREQUAL "TRUE")
set (CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-as-needed")
add_library(HelloWorldModule SHARED "helloworld/HelloWorldModule.cpp" "../../include/modules/helloworld/HelloWorldModule.h")
target_link_libraries(HelloWorldModule luatables lua pthread boost_system-mt boost_log-mt boost_log_setup-mt boost_thread-mt)
set(Boost_USE_MULTITHREADED ON)
else()
set (CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-as-needed")
add_library(HelloWorldModule SHARED "helloworld/HelloWorldModule.cpp" "../../include/modules/helloworld/HelloWorldModule.h")
target_link_libraries(HelloWorldModule luatables lua5.1 dl pthread boost_system boost_log_setup boost_log boost_thread dl)
set(Boost_USE_MULTITHREADED ON)
endif()
In this cmake script, we create a target called HelloWorldModule which is a shared library (will be built in the lib folder (which is important later on)) - this library depends on the boost libraries for logging, the luatables library for configuration files, and dl for the dynamic linking. We also set boost multi-threading to on, which allows for multithreading in the boost systems. Notice how we have two sections. In some cases, a module might run differently on a robot, vs on a computer (that is, a robot may make calls to the DCM, while a computer may make calls to a simulation program). In this case, the compilation may be different for both, so the if statement allows us to change compilation depending on the target. In our case, the module is the same, and thus, the same code is used in both halves of the if statement.
Now we're done linking the module in, all we have left is configuration files and testing.
Each module has to be specified by a ".module" configuration file. We give the ".module" file below:
module = {
name = "Hello World Module",
liblocation = "../lib/libHelloWorldModule.so",
rfps = 20,
mprio = 4,
mthr = 1
}
return module
In this configuration file, we give the name of the module, the location of the shared library (../lib/lib*Module Name Here*
), the requested FPS of the module, the priority of the module (0-9, 9 the lowest), and the thread that the module should run on (1-NUM_THREADS). We then place this file as "helloworld.module" in the config/modules
folder in the root directory.
To test our module, all we have to do is edit the file config/main.config
, so that it looks more like this:
main = {
len_init_modules = 2,
-- These are the names of the .module files in the config/modules folder
init_modules = {
"testmoduleone.module",
"helloworld.module"
}
}
return main
That is, we add the ".module" file to the initialization of the system (that is, it's loaded at runtime, and not by another module). Make sure len_init_modules
is set to two. We then compile and build the module! To compile and build, go to the root /NAO/NAO-engine/
directory and run ./build.sh local
. Then enter into the /build-local/bin/
folder and run sudo ./pineapple
. It should now be running on our system!!!
For more reading, check out the following tutorials!
For a tutorial on processing intents in your module, go HERE
For a tutorial on using the inter-module memory store (The Bazaar) go HERE
For a tutorial on scheduling and module configuration go HERE
For a tutorial on remote intent scheduling go HERE
Copyright (c) 2017 "University of Denver"
- Getting Started
- Architecture
- Build System
- The Vagrant Environment
- The Robot Environment
- Misc