How to Implement a Custom Observer

After having implemented the custom ExampleUnit, this section will explain how to implement the matching observer, ExampleUnitObserver. An observer consists of three parts:

  • The observer interface ExampleUnitObserverInterface
  • The observer component ExampleUnitObserver
  • The observer application ExampleUnitObserverApp

The following sections will discuss these steps in detail.

The Interface

The ExampleUnitObserverInterface is the interface that will be implemented by the observer component. This interface is only needed, because an observer needs to implement at least two interfaces: The general observer interface and the interface of the units, it wants to listen to. In this case the observer-interface inherits from ObserverInterface and ExampleUnitListener.

module armarx
interface ExampleUnitObserverInterface extends ObserverInterface, ExampleUnitListener

The Component

Both interfaces that ExampleUnitObserverInterface inherits need to be implmeneted in the observer-component:

class ARMARXCORE_IMPORT_EXPORT ExampleUnitObserver :
virtual public Observer,
virtual public ExampleUnitObserverInterface
std::string getDefaultName() const override
return "ExampleUnitObserver";
void onInitObserver() override;
void onConnectObserver() override;
void reportPeriodicValue(const VariantBasePtr& value, Ice::Double timestamp, const ::Ice::Current& = Ice::emptyCurrent) override;
PropertyDefinitionsPtr createPropertyDefinitions() override;
using Observer::removeDatafield;
using ExampleUnitObserverPtr = IceInternal::Handle<ExampleUnitObserver>;

In the observer-initialization process, determined by the methods onInitObserver and onConnectObserver, the observer defines the resources it requires and produces. The observer calls usingTopic to subscribe to a specific topic. The system takes care of postponing the call of onConnectObserver until the respective topic is found. After that the observer states which channels it will offer (offerChannel) and which data fields will be available in these channels (offerDataField). Each data field is a stream of data of a single type. In this case, the observer uses the data published under the topic "PeriodicExampleValue" and offers a channel called "exampleChannel". This channel consists of two data fields: "exampleDataFieldType" for the type of the value published by ExampleUnit and "exampleDataFieldInt" for the value, if it is an integer.

Finally, two ConditionChecks are offered. By calling offerConditionCheck, the observer states that its data fields allow the respective condition checks. The ConditionHandler takes care that conditions are only installed for the data types they are defined for.

void armarx::ExampleUnitObserver::onInitObserver()
offerConditionCheck("larger", new ConditionCheckLarger());
offerConditionCheck("smaller", new ConditionCheckSmaller());
void armarx::ExampleUnitObserver::onConnectObserver()
offerChannel("exampleChannel", "A constant value periodically published by ExampleUnit");
offerDataField("exampleChannel", "exampleDataFieldInt", VariantType::Int, "The value published by ExampleUnit (if it is in integer)");
offerDataField("exampleChannel", "exampleDataFieldFloat", VariantType::Float, "Another field");
offerDataField("exampleChannel", "exampleDataFieldType", VariantType::String, "The type of the value published by ExampleUnit");
offerDataField("exampleChannel", "dataReportDelay", VariantType::Double, "The delay of the reporting from the unit to the observer");
void armarx::ExampleUnitObserver::reportPeriodicValue(const armarx::VariantBasePtr& value, Ice::Double timestamp, const Ice::Current&)
double delay = IceUtil::Time::now().toMicroSecondsDouble() - timestamp;
ARMARX_INFO_S << "Delay: " << delay << deactivateSpam(1);
VariantPtr v = VariantPtr::dynamicCast(value);
if (v->getType() == VariantType::Int)
setDataFieldFlatCopy("exampleChannel", "exampleDataFieldInt", v);
else if (v->getType() == VariantType::Float)
setDataFieldFlatCopy("exampleChannel", "exampleDataFieldFloat", v);
setDataField("exampleChannel", "exampleDataFieldType", v->getTypeName());
setDataField("exampleChannel", "dataReportDelay", delay);

The method reportPeriodicValue gets called whenever ExampleUnit publishes a new value. The value is forwarded to the two data fields by calling setDataField. A call to updateChannel starts the re-evaluation of all conditions installed on the respective channel.

The Application

The application that makes the observer executable is implemented in the same manner as the ExampleUnit-application:

int main(int argc, char* argv[])
return armarx::runSimpleComponentApp<armarx::ExampleUnitObserver>(argc, argv, "ExampleUnitObserver");

As any other component, observers can define properties. This happens in the same manner as for units.

The Final Setup

Now, two new components exist: A unit and an observer. Once both components are startetd, they start to interact with each other. This interaction can be examined using the ObserverView Gui Widget.

const VariantTypeId Float
Definition: Variant.h:918
::IceInternal::Handle<::armarx::VariantBase > VariantBasePtr
Definition: ManagedIceObject.h:109
Definition: forward_declarations.h:8
const VariantTypeId Double
Definition: Variant.h:919
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
Definition: Logging.cpp:72
IceInternal::Handle< Variant > VariantPtr
Definition: Variant.h:42
std::shared_ptr< Value > value()
Definition: cxxopts.hpp:926
double v(double t, double v0, double a0, double j)
Definition: CtrlUtil.h:39
const VariantTypeId Int
Definition: Variant.h:916
std::string String
Definition: cxxopts.hpp:209
Definition: Logging.h:195
Definition: ImportExport.h:38
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
Definition: forward_declarations.h:34
int main(int argc, char *argv[])
Definition: Admin.cpp:45
This file offers overloads of toIce() and fromIce() functions for STL container types.
Definition: ArmarXTimeserver.cpp:28