MIRA
Units


Contents

Overview

Units are the modules of possibly large and complex modular applications that perform the actual tasks and computations. They implement the algorithms, e.g. in robotics applications for path planning or localization. For communication with other Units, message passing via Channels or Remote Procedure Calls can be used.

Units are implemented as shared libraries. They are loaded and instantiated and finally executed when a configuration file is launched by the mira or miracenter tool.

Units are derived from Authorities and provide automatic checkin and checkout. There are two Unit base classes available:

For a tutorial on how to write and use units please see Writing a Unit.

MicroUnits

If a unit subscribes a channel and wants to process the data immediately when it arrives, no separate worker thread is necessary. In those cases a MicroUnit is suitable.

It provides multiple methods that can be reimplemented in a derived class. All methods get called (by default) from within the same thread - so no mutexes are required:

It is not mandatory to implement any of these methods in your derived class.

Never use functions that interact with the framework in the Unit's constructor, since the Unit will not be checked in yet at this time. Always use the initialize() method to interact with the framework for the first time.

The following example creates a MicroUnit that subscribes on the "Pose" channel. The specified callback will be called each time new data becomes available within the channel.

#include <fw/MicroUnit.h>
using namespace mira;
class MySubscriberUnit : public MicroUnit
{
MIRA_OBJECT(MySubscriberUnit)
public:
void poseCallback(ChannelRead<Pose2> value)
{
MIRA_LOG(NOTICE) << "MyUnit: Got callback.";
}
virtual void initialize()
{
subscribe<Pose2>("Pose", &MySubscriberUnit::poseCallback);
}
};
MIRA_CLASS_SERIALIZATION( MySubscriberUnit, MicroUnit );

MicroUnits are always reactive. To do a task actively, one can add multiple worker threads (Timers) using the createTimer() method. But there is already a class that provides the user with a configurable worker thread - its called a Unit.

Units

Units are derived from MicroUnits and therefore provide all their functionality. The Unit additionally provides a process() method that is called periodically. This process() method can be used as a worker thread which usually is necessary for Units that produce data. Hence, Units are usually suitable for data sources, while MicroUnits are suitable for data sinks. The process method is realized using the createTimer() method and therefore the process method takes a Timer parameter that can be used to get statistics about the calls to process(). The following example shows a simple publisher Unit that writes data into channel within its process() method that gets called every 500 milliseconds by default:

#include <fw/Unit.h>
using namespace mira;
class MyPublisherUnit : public Unit
{
MIRA_OBJECT(MyPublisherUnit)
public:
MyPublisherUnit() :
Unit(Duration::milliseconds(500))
{
}
virtual void initialize()
{
poseChannel = publish<Pose2>("Pose");
}
// gets called every 500ms if operational
virtual void process(const Timer& timer)
{
ChannelWrite<Pose2> pose = poseChannel.write();
pose->timestamp = Time::now();
// write something to pose
}
Channel<Pose2> poseChannel;
};
MIRA_CLASS_SERIALIZATION( MyPublisherUnit, Unit );

Serialization and Parameters

Like any class, a Unit can be made "serializable" by adding a reflect() method that reflects all members and properties of the unit. In fact, each Unit is created by deserializing it from the configuration file that specifies all units that belong to an application. Hence, all properties and members specified in the Unit's reflect() method can be directly set as parameters within the configuration file. Moreover, members that are reflected as "properties" can be modified live at runtime, e.g. using the Property View of the miracenter.

When a Unit or MicroUnit is instantiated, the reflect() method is called after the construction of the Unit but before initialize() is called. Hence, the order of method calls during instantiation is as follows:

  1. the Unit is instantiated and its constructor is called
  2. reflect() is called to deserialize all members and parameters
  3. initialize() is called
  4. resume() is called
When adding a reflect method to serialize properties and members of your unit, you must NOT forget to call the reflect method of the base class.
class MyPublisherUnit : public Unit
{
...
template<typename Reflector>
void reflect(Reflector& r)
{
// call base class reflect !!!
r.member("MyMember", member, "A member");
}
...
int member;
};

Recovery Mode

Beside the normal operational mode (for Units it means the process() method is called in the specified interval), the MicroUnit class provides a recovery mode. If at any point your Unit is not able to process in its normal way, it can enter the recovery mode by calling needRecovery(). In that mode the recover() method gets called periodically (for Units it is called instead of the process() method). This can be used to recover from broken connections or drivers problems by running a certain re-initialization procedure. If the recovery is successful, the unit can enter the normal operational mode by calling operational() (for Units the process() method is called again).

In the following the above example was extended by a recovery mechanism that is activated whenever the connection to the driver is disrupted in order to reestablish the connection:

#include <fw/Unit.h>
using namespace mira;
class MyPublisherUnit : public Unit
{
MIRA_OBJECT(MyPublisherUnit)
public:
MyPublisherUnit() :
Unit(Duration::milliseconds(500))
{
}
virtual void initialize()
{
poseChannel = publish<Pose2>("Pose");
// set status
bootup("Connecting to driver");
// keep trying
while(!boost::this_thread::interruption_requested())
{
connect();
if (myDriver.connected())
break;
}
}
void connect()
{
myDriver.reset();
myDriver.connect();
}
// gets called every 500ms if operational
virtual void process(const Timer& timer)
{
// if connection is lost recover
if (!myDriver.connected())
{
needRecovery();
return;
}
ChannelWrite<Pose2> pose = poseChannel.write();
pose->timestamp = Time::now();
// write something to pose
}
// gets called cyclic if recover mode is on
virtual void recover()
{
connect();
// if connected again set operational mode
if (myDriver.connected())
operational();
}
MyDriver myDriver;
Channel<Pose2> poseChannel;
};
MIRA_CLASS_SERIALIZATION( MyPublisherUnit, Unit );