Anda di halaman 1dari 4

Data Port basics

This article provides the minimum information that everybody working with data ports should know. All
other relevant features for component and application development are covered in Advanced/Data
Ports.
Data flow is the primary communication paradigm in Finroc. Especially on lower levels of robot control
systems, components (modules, groups) exchange informationen via data flow edges (e.g. in control
loops). The Finstruct tool visualizes a set of connected components as a data flow graph.
Component interfaces consist of a set of ports. Data ports are used to easily transfer data from one
component to all connected components. There are input and output ports: Output ports are used
topublish data. Input ports are used to receive data from another component. Finally, there are routing
ports. They are used in group interfaces and forward data to all connected input ports. From outside
the group, they look like input or output ports.
In our tools, the following graphical representation is used:

Data Types
Each port class has a template parameter T. This is the type of data transferred via this port. Only
ports that have compatible data types can be connected.
T can be any C++ data type that can be serialized to a binary stream using our serialization
library rrlib_serialization. SeeAdvanced/Suitable Port Data Types on making C++ data types
serializable.

Creating ports
Component and application developers typically use the convenient port classes defined in module and
group classes - e.g. tInput andtOutput in case of a simple tModule.
To add a port to a component interface, add it as a public member variable to the component class in
the component's header file. In the provided code templates, there is a section for ports:
//---------------------------------------------------------------------// Ports (These are the only variables that may be declared public)
//---------------------------------------------------------------------public:
To add, for instance, an output port of type double, it is sufficient to add it here:
tOutput<double> output_port
The port names are automatically generated from variable names in source code. This port would be
called "Output Port" - unless a different port name is provided in the constructor.

Constructor parameters
Data ports have many features which can be enabled via constructor parameters - see Advanced/Data
Ports. A port constructor with all these possible parameters would have a lot of parameters. However,

only few of them are typically used. Specifying bounds, for instance, is only allowed for suitable types.
That's why we decided to give ports a constructor that takes a variadic list of parameters.
The doxygen comment says about all there is to say:
/*!
* Constructor takes a variadic argument list... just any properties you want to
assign to port.
*
* Port name and parent are usually determined automatically (however, only possible
when port is direct class member).
* If this is not possible/desired, name needs to be provided as first constructor
argument - parent as arbitrary one.
*
* A string as first parameter is interpreted as port name; Any other or further
string as config entry (irrelevant for ports).
* A framework element pointer is interpreted as parent.
* tFrameworkElement::tFlags arguments are interpreted as flags.
* A tQueueSettings argument creates an input queue with the specified settings.
* tBounds<T> are port's bounds.
* tUnit argument is port's unit.
* const T& is interpreted as port's default value.
* tPortCreationInfo<T> argument is copied. This is only allowed as first argument.
*
* This becomes a little tricky when T is a string type. There we have these rules:
* A String not provided as first argument is interpreted as default value.
* Any further string is interpreted as config entry.
*/
To specify any constructor arguments (which is not required), add the variable to the component
constructor's initializer list. The list is marked with the following line, in the component code templates:
If you have some member variables, please initialize them here. Especially
built-in types (like pointers!). Delete this line otherwise!
For the example, this could look like this:
output_port("Other name for output port", 10)
The port would now have an initial value of 10 and a different name.

Connecting ports
Ports can be connected if their types are compatible. Basically, connecting can either be done in source
code - or graphically using finstruct. Both is covered in the Simple Robot Simulation Tutorial.
In source code, ports are connected using their ConnectTo methods (see tPortWrapperBase). This is
usually done in some group constructor.
module1->output_port.ConnectTo(module2->input_port);
To connect many ports at once, the ConnectByName methods in tPortGroup can be handy.
module1->GetSensorOutputs().ConnectByName(module2->GetSensorInputs());

Publishing data via output ports


Data can be published via output ports using the following pattern (we have an output port of
type double in this example):
data_ports::tPortDataPointer<double> buffer_to_publish =
output_port.GetUnusedBuffer();

*buffer_to_publish = 42;
buffer_to_publish.SetTimestamp(timestamp); // optional
output_port.Publish(buffer_to_publish);
tPortDataPointer is a smart pointer class for Finroc data port buffers. Usage is similar
to std::unique_ptr (as it is movable).

Receiving data via input ports


Data can be received using a port's GetPointer() method.
data_ports::tPortDataPointer<const double> received_buffer = input_port.GetPointer();
tPortDataPointer ensures that the buffer can safely be accessed until it goes out of scope. Note that
received buffers are read-only (enforced by the const keyword).

Changed flags
Possibly, modules only want to trigger execution of code, if a port value has changed since the last
cycle (last call of Update(), Sense() orControl()).
They can check this via a port's HasChanged() method:
if (input_port.HasChanged())
{
// do something
}
It is also possible to check whether any of the component's input ports has changed - e.g.:
if (this->SensorInputChanged())
{
// At least one of your sensor input ports has changed. Do something useful with
its data.
}
Changed flags are automatically reset after executing Update(), Sense() or Control().
Note: The changed flag is always set when a port's buffer changes - even if this new buffer has
the same value and timestamp.
It is guaranteed that the changed flag is set at least once on each incoming buffer (=> no buffer
change gets lost). In rare cases, however, an incoming buffer can cause the changed flag to be set in
two consecutive cycles. This is due to the lock-free port implementation.
If this is not acceptable, a module should maintain a copy of the last port value and compare this with
any new value in order to detect changes.

Simplifications for cheaply copied types


Finroc data ports provide an optional more convenient API for cheaply copied types.
Cheaply copied types provide computationally inexpensive copy constructors and copy assignment
operators. In particular, they do not manage any memory internally (as this is problematic in real-time
code).
For such types, publishing can be done with a simple call to a port's Publish method (again,
a double port in this example):
output_port.Publish(42, optional_timestamp);

Values can be received using Get():


double current_port_value = input_port.Get(optional_timestamp_buffer);

Port API
Ports provide a lot more functionality.
For output ports, the API can found in the these three classes: tOutputPort tPort tPortWrapperBase
For input ports, the API can found in the these three classes: tInputPort tPort tPortWrapperBase

Anda mungkin juga menyukai