orca-robotics INTRODUCTION Overview Download and Install Quick Start Documentation Publications REPOSITORY Interfaces Components Libraries Utilities Software Map DEVELOPER Tutorials Examples Dev Guide Dashboard Wiki login/pass: orca/orca PEOPLE Contributors Users Project Download Mailing lists
|
Writing a super-simple component
Source filesGo to the top directory of the Orca distribution and type the following:
$ ls src/examples/brick brick.def doc.dox CMakeLists.txt mainthread.cpp mainthread.h What are all these files? Here we'll just list their purpose, click on the links for the line-by-line explanation of the source.
CMakeLists.txtORCA_SET_COMPONENT_NAME( Brick ) SET( build TRUE ) # Check for human input GBX_REQUIRE_OPTION( build EXE ${APP_NAME} ON ) # libs on which our code depends would go here # SET( dep_libs ??? ) # GBX_REQUIRE_TARGETS( build EXE ${APP_NAME} ${dep_libs} ) IF( build) INCLUDE( ${ORCA_CMAKE_DIR}/UseComponentRules.cmake ) FILE( GLOB srcs *.cpp ) # build stand-alone component ORCA_WRAP_MAINTHREAD_INTO_APP( ${APP_NAME} ${APP_CTRLC_HANDLER} ${COMP_NAMESPACE} ${COMP_NAME} ${srcs} ) # TARGET_LINK_LIBRARIES( ${APP_NAME} ${dep_libs} ) # build IceBox service IF( ORCA_BUILD_SERVICES ) ORCA_WRAP_MAINTHREAD_INTO_SERVICE( ${SERVICE_NAME} ${COMP_NAMESPACE} ${COMP_NAME} ${srcs} ) # TARGET_LINK_LIBRARIES( ${SERVICE_NAME} ${dep_libs} ) ENDIF( ORCA_BUILD_SERVICES ) ORCA_GENERATE_CONFIG_FILE( ${APP_NAME}.def ) ENDIF( build )
ORCA_SET_COMPONENT_NAME ( Brick )
Also sets APP_CTRLC_HANDLER=1 (instructs application to install Ctrl-C Handler) For custom values, call this function first and then override some or all of the variables manually. For example: ORCA_SET_COMPONENT_NAME( MyComponent ) SET( COMP_NAMESPACE mycomp )
SET( build TRUE ) BUILD and set it to TRUE, i.e. "this library will be built". Failure to meet any one of the requirements will assign FALSE to this "cumulative variable".
# Check for human input GBX_REQUIRE_OPTION( build EXE ${APP_NAME} ON ) BUILD_BRICK (BUILD + the name of our component in upper case). This option can be used as a variable to implement conditional compilation. The default value of this variable is ON, equivalent to TRUE. CMake comes with a configuration tool called ccmake . In Linux, when you type "ccmake ." in the top directory of the Orca distribution, you will see a list of all compilation options. Scroll down to BUILD_BRICK and press ENTER, the value of the option will change to OFF. Now configure (press c ) and generate (press g ). The build options have now been adjusted such that the Brick example will not be compiled. Repeat the process to turn the compilation back on or delete CMakeCache.txt to go back to defaults.
# libs on which our code depends would go here # SET( dep_libs ??? ) # GBX_REQUIRE_TARGETS( build EXE ${APP_NAME} ${dep_libs} )
IF( build)
INCLUDE( ${ORCA_CMAKE_DIR}/UseComponentRules.cmake ) UseComponentRules.cmake and located in [ORCA-SRC]/cmake directory. The CMake INCLUDE command imports all the commands from that file as if we typed them in here by hand.
Because the
FILE( GLOB srcs *.cpp ) COMP_SRCS .
ORCA_WRAP_MAINTHREAD_INTO_APP( ${APP_NAME} ${APP_CTRLC_HANDLER} ${COMP_NAMESPACE} ${COMP_NAME} ${srcs} )
If your component is non-standard (e.g. has several threads or needs to do something special in the constructor) you can create a custom Orca component. There's a macro for this case as well called WRAP_COMPONENT_INTO_APP which will place the custom component into an Orca application. If your
# TARGET_LINK_LIBRARIES( ${APP_NAME} ${dep_libs} )
IF( ORCA_BUILD_SERVICES ) ORCA_WRAP_MAINTHREAD_INTO_SERVICE( ${SERVICE_NAME} ${COMP_NAMESPACE} ${COMP_NAME} ${srcs} ) # TARGET_LINK_LIBRARIES( ${SERVICE_NAME} ${dep_libs} ) ENDIF( ORCA_BUILD_SERVICES ) BUILD_SERVICES is ON, create a shared library called libOrcaBrick.so (default value for ${SERVICE_NAME} ). The macro wraps an Orca service around a single thread. The BUILD_SERVICES option is defined globally but can be user-configured with ccmake tool. The default setting is OFF. The macro will also schedule this service library to be installed into [ORCA-INSTALL]/lib. We could also link this library to dependencies.
ORCA_GENERATE_CONFIG_FILE( ${APP_NAME}.def )
ENDIF( build ) There are additional sources of information on CMake. Take a look at the CMake documentation and a GearBox build system tutorial. mainthread.h#ifndef MAIN_THREAD_H #define MAIN_THREAD_H #include <gbxsickacfr/gbxiceutilacfr/subsystemthread.h> #include <orcaice/context.h> namespace brick { class MainThread: public gbxsickacfr::gbxiceutilacfr::SubsystemThread { public: MainThread( const orcaice::Context& context ); // from SubsystemThread virtual void walk(); private: orcaice::Context context_; }; } // namespace #endif
#include <gbxsickacfr/gbxiceutilacfr/subsystemthread.h> #include <orcaice/context.h>
class MainThread: public gbxsickacfr::gbxiceutilacfr::SubsystemThread Firstly, it catches all common and uncommon exception which may originate in our code. This does not mean that we should not worry about exceptions because when SubsystemThread catches an exception it does not know what to do, so it just prints out a statement and waits for the component to shut down. This behavior is useful because, without this precaution, the program would simply seg fault and you wouldn't event know what happened. Secondly, this class simplifies usage of hydroutil::Status class by integrating functionality of hydroutil::SubStatus. Status can keep track of multiple subsystems which can be defined arbitrarily and are destinguished by a name. Typically, each thread is a subsystem. It can be in certain state and this state can be reported through the hydroutil::Status facility. If something about the way hydroutil::SubsystemThread behaves doesn't suit your needs, you can always use the more general hydroutil::Thread.
public: MainThread( const orcaice::Context& context );
virtual void walk(); walk() defined in hydroutil::SubsystemThread. Function walk is the entry point of the thread, the thread execution happens in it. (The equivalent function in the more general hydroutil::Thread is called run() , i.e. walking is safer than running. :)
private: orcaice::Context context_; }; mainthread.cpp#include <iostream> #include <orcaice/orcaice.h> #include "mainthread.h" using namespace std; using namespace brick; MainThread::MainThread( const orcaice::Context& context ) : SubsystemThread( context.tracer(), context.status(), "MainThread" ), context_(context) { subStatus().setMaxHeartbeatInterval( 20.0 ); } void MainThread::walk() { // multi-try function orcaice::activate( context_, this, subsysName() ); // // Read configuration settings // std::string prefix = context_.tag() + ".Config."; int sleepIntervalMs = orcaice::getPropertyAsIntWithDefault( context_.properties(), prefix+"SleepIntervalMs", 1000 ); subStatus().ok( "Initialized" ); // // Main loop // subStatus().setMaxHeartbeatInterval( sleepIntervalMs * 3.0/1000.0 ); while( !isStopping() ) { context_.tracer().debug( "Running main loop", 5 ); subStatus().heartbeat(); // here we can do something useful IceUtil::ThreadControl::sleep(IceUtil::Time::milliSeconds(sleepIntervalMs)); } }
MainThread::MainThread( const orcaice::Context& context ) : SubsystemThread( context.tracer(), context.status(), "MainThread" ), context_(context) {
subStatus().setMaxHeartbeatInterval( 20.0 ); Note that this line is equivalento to calling. context_.status().setMaxHeartbeatInterval( subsysName(), 20.0 );
void MainThread::walk() { Executing code in a separate thread usually means that we are responsible for catching all possible exceptions. We don't do it here because hydroutil::SubsystemThread does it for us.
orcaice::activate( context_, this, subsysName() ); The component does not perform this step automatically and leave it up to the designer because this is an important step and you may want to choose when to do it. For example you may want to setup all your required interfaces (i.e. clients) first and then enable the provided interfaces.
This operation may fail for various reasons. Common ones are: the network cable is unplugged, the Registry is unreachable. This standard function will try to activate indefinitely after sleeping for a few seconds. But we want to be able to get out of this infinite loop (e.g. with a Ctrl-C). For this reason, we supply this function with a pointer to a Thread (i.e.
std::string prefix = context_.tag() + ".Config."; int sleepIntervalMs = orcaice::getPropertyAsIntWithDefault( context_.properties(), prefix+"SleepIntervalMs", 1000 ); This component has a single configuration parameter specifying the sleep interval within the main loop. It must be specified as an integer in milliseconds. See Brick documentation for the description of this config parameter. We use a helper function from libOrcaIce which returns an integer with default. The value is looked up in the property set available from the component context using the key "Brick.Config.SleepIntervalMs". If this property is absent in the config file, the default value of 1000ms = 1s is used. Note that we should probably do more checks on the specified value, e.g. make sure that it is not negative.
subStatus().ok( "Initialized" );
subStatus().setMaxHeartbeatInterval( sleepIntervalMs * 3.0/1000.0 );
while( !isStopping() ) {
context_.tracer().debug( "Running main loop", 5 );
subStatus().heartbeat();
// here we can do something useful IceUtil::ThreadControl::sleep(IceUtil::Time::milliSeconds(sleepIntervalMs));
} } brick.defDefaultComponentTag=Brick # Provided Interfaces # none # Required Interfaces #none # Component Configuration Config.SleepIntervalMs.Default=1000
DefaultComponentTag=Brick brick.cfg file.
# Provided Interfaces # none # Required Interfaces #none
# Component Configuration Config.SleepIntervalMs.Default=1000 brick.cfg file "Brick.Config.SleepIntervalMs=1000"What's Next?When you are done, go to the next tutorial: Running a super-simple component. |
Webmaster: Tobias Kaupp (tobasco at users.sourceforge.net)