wiki:PluginHowto
Last modified 3 years ago Last modified on 21.07.2015 14:26:51

How to Write a Fawkes Plugin

In Fawkes plugins are were the "magic" happens, they form the software and functional components. They can have various purposes. Some plugins integrate with hardware platforms, acquire data from a particular sensor or control a certain actuator. Other plugins process sensor data to obtain an object's or the robot's position. Yet again other plugins deal with task coordination and behavior execution. This little tutorial will introduce the generic aspects of writing a plugin which are common to all kinds of plugins.

You should have completed the GettingStartedGuide and DeveloperGettingStartedGuide to get familiar with Fawkes in general and start developing. We assume that you are at least somewhat familiar with the Fawkes terminology.

Building Blocks

Each plugin consists of one or more threads which can either be hooked into a main loop shared with all other threads in Fawkes, or concurrent to Fawkes and the other threads. Plugins communicate with each other using the BlackBoard. It stored data structures called Interfaces and commands can be send using messages. Threads within a plugin also often communicate via the blackboard. But sometimes it is required or useful that they share some memory. Plugins are configured from a central subsystem in Fawkes that reads values from a YAML-based configuration store. Likewise plugin use a centralized logging system and a shared clock. All these (and more) features are easily accessible using aspects. Think of them as tags that you give your thread class that indicate the requirements to Fawkes which it will then assert for you.

Writing the Plugin Skeleton

At the very least, a plugin typically has four files. Let's assume your plugin is called "genesis". Then you would have the following files.

Makefile
Build instructions, libraries to link against, checks for third party software.
genesis_plugin.cpp
Plugin definition and thread instantiation.
genesis_thread.h
The header including the declaration of your thread.
genesis_thread.cpp
The module file containing the definition of your thread and all its methods.

Please see the files at the end. The example plugin will open a blackboard interface and in each cycle print out the position of the robot to the log, or a warning if no localization is currently running.

Note that in the Makefile the plugin is build only under the condition that TF, the Fawkes transforms framework, is available. Otherwise a warning is printed.

The GenesisThread has three main methods besides the constructor, which is only used to initialize inherited classes. First, the init() method is called as soon as the framework has asserted the required resources. In our example, it is used to open the pose interface. Since we open it for reading, it will most likely never fail. If it failed, it would throw an exception. We do not catch that exception here, we just let it escalate. And this is on purpose. The way to indicate to the framework that some initialization could not be completed (and hence the thread should not be run) is to throw an exception. The exception is caught and the message printed. Afterwards the plugin is unloaded. Note that in more complex plugins you might need to catch the exception to run cleanup routines, close interfaces, and free memory, before re-throwing the exception. Next, the finalize() method is used to tear down a thread after it has been stopped, i.e. when the plugin is unloaded (only if it was running before, not on a failure in init()!). Here, we close the interface we opened in init(). Finally, the loop() method is continuously run until the thread is stopped. In our case, because we will hook up the thread in the main loop, it is called once per main loop iteration. In each cycle, we check if there is a writer for the pose interface. If there is we print the pose as a 2D coordinate with orientation, otherwise we print a warning.

Also note that in the thread code there is no check if the required resources are available. Because we assert them with aspects, we can be certain that either all the requirements were met by the framework, otherwise the thread would never have been started. An aspect is given to a thread by inheriting from the aspect class from the aspect library. In genesis_thread.h you see that the GenesisThread not only inherits from Thread, but from several aspects. The LoggingAspect provides access to the logger, hence if the thread runs it is guaranteed that the "logger" variable from the aspect class has been properly initialized by the framework as soon as init() is run. Likewise the BlackBoardAspect gives us access to the blackboard. The BlockedTimingAspect hooks the thread into the main loop.

In genesis_thread.cpp we see the implementation of the thread. The method have been explained above. Something to look closer to is the constructor. Each Thread needs a (ideally unique) name and an operation mode. Additionally, we setup the blocked timing execution, i.e. we hook the thread into the main loop at the world state stage. For now all you need to know is that it runs after sensor data processing and hence the position has been calculated at that time.

Especially as a newcomer you should pay close attention to the little things. They make your work shine and show your appreciation for details. For example, on creating new files add proper headers as shown in the examples, have proper descriptions, copyrights and dates. When editing files keep the existing style and be gentle with your modifications. However, nothing should stop you from making the changes necessary. Read about the CodingStyle and our GitNotes. Have fun in what you are doing to do it best!

Files

Here are the files that make up the example gensis plugin to print pose information to the log. To try it out, create them in a directory src/plugins/genesis in your Fawkes git working copy.

Makefile

#*****************************************************************************
#               Makefile Build System for Fawkes: Genesis Plugin
#
#   Created on Thu Sep 27 14:23:00 2012
#   Copyright (C) 2011 by Tim Niemueller, AllemaniACs RoboCup Team
#
##*****************************************************************************
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#*****************************************************************************

BASEDIR = ../../..

include $(BASEDIR)/etc/buildsys/config.mk
include $(BUILDCONFDIR)/tf/tf.mk

LIBS_genesis = fawkescore fawkesutils fawkesaspects fvutils \
	       fawkestf fawkesinterface fawkesblackboard \
	       Position3DInterface
OBJS_genesis = genesis_plugin.o genesis_thread.o

OBJS_all    = $(OBJS_genesis)

ifeq ($(HAVE_TF),1)
  CFLAGS  += $(CFLAGS_TF)
  LDFLAGS += $(LDFLAGS_TF)

  PLUGINS_all = $(PLUGINDIR)/genesis.$(SOEXT)
else
  WARN_TARGETS += warning_tf
endif

ifeq ($(OBJSSUBMAKE),1)
all: $(WARN_TARGETS)
.PHONY: warning_tf warning_ros warning_geometry_msgs
warning_tf:
	$(SILENT)echo -e "$(INDENT_PRINT)--> $(TRED)Omitting genesis libs" \
                         "$(TNORMAL) (tf not available)"
endif

include $(BUILDSYSDIR)/base.mk

genesis_plugin.cpp

/***************************************************************************
 *  genesis_plugin.cpp - Example plugin
 *
 *  Created: Thu Sep 27 14:25:00 2012
 *  Copyright  2012  Tim Niemueller [www.niemueller.de]
 ****************************************************************************/

/*  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Library General Public License for more details.
 *
 *  Read the full text in the LICENSE.GPL file in the doc directory.
 */

#include <core/plugin.h>

#include "genesis_thread.h"

using namespace fawkes;

/** Plugin to ...
 * @author Tim Niemueller
 */
class GenesisPlugin : public fawkes::Plugin
{
 public:
  /** Constructor.
   * @param config Fawkes configuration
   */
  GenesisPlugin(Configuration *config)
    : Plugin(config)
  {
    thread_list.push_back(new GenesisThread());
  }
};

PLUGIN_DESCRIPTION("Fawkes Tutorial Plugin")
EXPORT_PLUGIN(GenesisPlugin)

genesis_thread.h

/***************************************************************************
 *  genesis_thread.h - Thread to print the robot's position to the log
 *
 *  Created: Thu Sep 27 14:27:09 2012
 *  Copyright  2012  Tim Niemueller [www.niemueller.de]
 ****************************************************************************/

/*  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Library General Public License for more details.
 *
 *  Read the full text in the LICENSE.GPL file in the doc directory.
 */

#ifndef __PLUGINS_GENESIS_GENESIS_THREAD_H_
#define __PLUGINS_GENESIS_GENESIS_THREAD_H_

#include <core/threading/thread.h>
#include <aspect/clock.h>
#include <aspect/configurable.h>
#include <aspect/logging.h>
#include <aspect/blackboard.h>
#include <aspect/blocked_timing.h>

namespace fawkes {
  class Position3DInterface;
}

class GenesisThread
: public fawkes::Thread,
  public fawkes::ClockAspect,
  public fawkes::LoggingAspect,
  public fawkes::ConfigurableAspect,
  public fawkes::BlackBoardAspect,
  public fawkes::BlockedTimingAspect
{
 public:
  GenesisThread();

  virtual void init();
  virtual void loop();
  virtual void finalize();
 private:
  fawkes::Position3DInterface *pose_if_;
};

#endif

genesis_thread.cpp

/***************************************************************************
 *  genesis_thread.cpp - Thread to print the robot's position to the log
 *
 *  Created: Thu Sep 27 14:31:11 2012
 *  Copyright  2012  Tim Niemueller [www.niemueller.de]
 ****************************************************************************/

/*  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Library General Public License for more details.
 *
 *  Read the full text in the LICENSE.GPL file in the doc directory.
 */

#include "genesis_thread.h"

#include <tf/types.h>
#include <interfaces/Position3DInterface.h>

using namespace fawkes;

/** @class GenesisThread "genesis_thread.h"
 * Thread to print robot position to log.
 * @author Tim Niemueller
 */

/** Constructor. */
GenesisThread::GenesisThread()
  : Thread("GenesisThread", Thread::OPMODE_WAITFORWAKEUP),
    BlockedTimingAspect(BlockedTimingAspect::WAKEUP_HOOK_WORLDSTATE)
{
}

void
GenesisThread::init()
{
  pose_if_ = blackboard->open_for_reading<Position3DInterface>("Pose");
}

void
GenesisThread::finalize()
{
  blackboard->close(pose_if_);
}

void
GenesisThread::loop()
{
  if (pose_if_->has_writer()) {
    pose_if_->read();
    double *r = pose_if_->rotation();
    tf::Quaternion pose_q(r[0], r[1], r[2], r[3]);
    logger->log_info(name(), "Pose: (%f,%f,%f)", pose_if_->translation(0),
                     pose_if_->translation(1), tf::get_yaw(pose_q));
  } else {
    logger->log_warn(name(), "No writer for pose interface");
  }
}