class PrintModule : public Module { public: PrintModule ( void ) : Module( "print", 0, false, false ) { }; void init( void ) { }; void uninit( void ) { }; void activate( void ); void deactivate( void ); void update( void ); };
Note that each class derived from the virtual base class Module must define five methods: init, uninit, activate, deactivate and update. These methods provide a a standard interface to the module manager which, among other tasks, keeps a list of all modules, manages their states, and calls the above methods at appropriate times. In the case of PrintModule, the uninit method does not do anything, the rest of the methods are defined below.
An important detail to note is that the Module class constructor takes four arguments. The first two determine the name and index of the module, which are used together to uniquely identify each module in the system. The last two arguments determine the type of the module and are explained in later chapters of this manual.
Activating a module usually involves making sure that all the components needed for the task are active and operational. Moreover, necessary initializations of the components must also be performed. For our example, the PrintModule will enable all the encoders using the hardware interface.
void PrintModule::activate( void ) { int axis; for ( axis = 0; axis < 6; axis++ ) EncoderHW::instance()->enable( axis ); }
An important detail to note is the way hardware components are accessed. Interfaces to hardware components are defined as abstract base classes (such as EncoderHW, DCMotorHW, DriveHW etc.). Each of these classes internally maintains a single instance of an actual implementation, which is initialized by the particular hardware library to be used. For those of you who are familiar with design patterns, this corresponds to a singleton design pattern. To be able to use a particular hardware component, the corresponding header file ("hardware/EncoderHW.hh" in this case) must be included.
As you may have guessed, the instance() method of such classes returns a pointer to the singleton instance of the corresponding hardware component, which can then be used to access the hardware functionality. A more detailed treatment of how hardware access is handled by RhexLib can be found in Hardware abstraction layer .
For our example, upon deactivation, the PrintModule will disable all the encoders using the hardware interface.
void PrintModule::deactivate( void ) { int axis; for ( axis = 0; axis < 6; axis++ ) EncoderHW::instance()->disable( axis ); }
Our example module, the PrintModule, will read the encoders and print their contents in its update method. Moreover, it will also be responsible for detecting keyboard input and exiting the program.
void PrintModule::update ( void ) { int axis; printf( "Encoders: " ); for ( axis = 0; axis < 6; axis++ ) printf( "0x%04x, ", EncoderHW::instance()->read( axis ) ); printf( "\n" ); if ( kbhit() ) { MMMessage( "User interrupt: Shutting down!\n" ); MMPowerOff( ); } }
The library function MMMainLoop() is the main entry point to the module manager, and only returns when the program is about to terminate. It is usually called once from main(), after all the desired modules are created and added to the module manager's list. Once this function is called, the module manager takes control and handles the periodic issues of module updates as well as other timing tasks. Following a call to MMMainLoop(), the only way to exit the program is through the MMPowerOff function, from within one of the module methods.
As such, there is a very typical way in which RHexLib programs are built. The following program is the main entry point to our small example and demonstrates how things are setup in a typical RHexLib program..
#include "ModuleManager.hh" int main( void ) { PrintModule pr; // Create an instance of the PrintModule module. // Initialize whichever hardware library we are linked against initHardware(); // Add and activate the print module MMAddModule( &pr, 10, 0, 10 ); MMActivateModule( &pr ); // Call the Module Manager main loop MMMainLoop(); // Cleanup underlying hardware before we exit cleanupHardware(); // Let the module manager do its final cleanup MMShutdown(); return 0; }
As this code segment shows, the typical structure of main() is as follows:
During the call to MMMainLoop(), the module methods will determine the flow of the execution. Note that at least one module needs to be activated before MMMainLoop is called. Otherwise, the program will be stuck in an infinite loop, and no module updates will take place.
An important detail is that the parameters of a module related to the scheduling of its updates are determined by the arguments to the MMAddModule() function call. It is hence possible to configure the period, the starting time offset and the update order (in case of multiple updates scheduled at the same time) of each module.