-
Notifications
You must be signed in to change notification settings - Fork 10
NewMessage
The goal of this tutorial is to declare and use a custom message type.
For our given node we want to add a simple message as a proof of concept.
Next Tutorial: How to make a new message ROS-convertible?
First of all an important thing to know:
Messages are not only registered as plug-ins, but have to be exported as a separate shared library. The reason for this design is, that other packages might want to use your message type and therefore have to be linked against your code.
For this reason there is an important convention: If you want to make classes available for other modules, export multiple shared objects: One for each plug-in type and one for exported classes.
This is why the first step to adding a new message type is to declare a new library.
First navigate to your project folder and create a new directory hierarchy include/csapex_tutorial
(next to src
).
Open your CMakeLists.txt
use this new folder:
Change
include_directories(
${catkin_INCLUDE_DIRS}
)
to
include_directories(
include
${catkin_INCLUDE_DIRS}
)
to allow you to use your own exported header files.
Next we will create our new message.
We create two files: src/tutorial_message.cpp
and include/csapex_tutorial/tutorial_message.h
Now we add a new library, which will contain our new message type. So add
add_library(csapex_tutorial
src/tutorial_message.cpp
)
target_link_libraries(csapex_tutorial_node
csapex_tutorial
${catkin_LIBRARIES}
)
install(TARGETS csapex_tutorial
ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION})
to your CMakeLists.txt
file.
Note that csapex_tutorial_node
is linked against csapex_tutorial
.
Next we have to export the header files and library for dependent packages by changing
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES csapex_tutorial
# CATKIN_DEPENDS csapex csapex_opencv
# DEPENDS system_lib
)
to
catkin_package(
INCLUDE_DIRS include
LIBRARIES csapex_tutorial
# CATKIN_DEPENDS csapex csapex_opencv
# DEPENDS system_lib
)
Fill the newly create files with the following:
#ifndef TUTORIAL_MESSAGE_H
#define TUTORIAL_MESSAGE_H
/// PROJECT
#include <csapex/msg/message_template.hpp>
/// SYSTEM
#include <yaml-cpp/yaml.h>
namespace csapex {
namespace connection_types {
struct TutorialMessage : public MessageTemplate<bool, TutorialMessage>
{
typedef std::shared_ptr<TutorialMessage> Ptr;
typedef std::shared_ptr<TutorialMessage const> ConstPtr;
TutorialMessage();
};
/// TRAITS
template <>
struct type<TutorialMessage> {
static std::string name() {
return "TutorialMessage";
}
};
}
}
/// YAML
namespace YAML {
template<>
struct convert<csapex::connection_types::TutorialMessage> {
static Node encode(const csapex::connection_types::TutorialMessage& rhs);
static bool decode(const Node& node, csapex::connection_types::TutorialMessage& rhs);
};
}
#endif // TUTORIAL_MESSAGE_H
/// HEADER
#include <csapex_tutorial/tutorial_message.h>
/// PROJECT
#include <csapex/utility/assert.h>
#include <csapex/utility/register_msg.h>
CSAPEX_REGISTER_MESSAGE(csapex::connection_types::TutorialMessage)
using namespace csapex;
using namespace connection_types;
TutorialMessage::TutorialMessage()
: MessageTemplate<bool, TutorialMessage>("/", 0)
{}
/// YAML
namespace YAML {
Node convert<csapex::connection_types::TutorialMessage>::encode(const csapex::connection_types::TutorialMessage& rhs)
{
Node node = convert<csapex::connection_types::Message>::encode(rhs);
node["value"] = rhs.value;
return node;
}
bool convert<csapex::connection_types::TutorialMessage>::decode(const Node& node, csapex::connection_types::TutorialMessage& rhs)
{
if(!node.IsMap()) {
return false;
}
convert<csapex::connection_types::Message>::decode(node, rhs);
rhs.value = node["value"].as<bool>();
return true;
}
}
which will generate a message that contains a bool
with the name value
.
Furthermore, the name of the message has to be registered using the csapex::connection_types::type
struct.
Finally the YAML
serialization functions have to be implemented.
Any node that has csapex_tutorial
as dependency can now use the message TutorialMessage
by including
#include <csapex_tutorial/tutorial_message.h>
For example, let us publish a TutorialMessage
in our node from the last tutorial:
/// PROJECT
#include <csapex/model/node.h>
#include <csapex/csapex_fwd.h>
#include <csapex/model/node_modifier.h>
#include <csapex_opencv/cv_mat_message.h>
#include <csapex/msg/io.h>
#include <csapex/param/parameter_factory.h>
#include <csapex_tutorial/tutorial_message.h>
/// SYSTEM
#include <csapex/utility/register_apex_plugin.h>
namespace csapex
{
namespace tutorial
{
class UserSwitch : public Node
{
private:
enum class Selector {
FIRST,
SECOND
};
public:
UserSwitch()
{
}
/// initialize the node
virtual void setup(csapex::NodeModifier& node_modifier) override
{
// Connector for the first input image
in_a_ = node_modifier.addInput<connection_types::CvMatMessage>("Image 1");
// Connector for the second input image
in_b_ = node_modifier.addInput<connection_types::CvMatMessage>("Image 2");
// Connector for the output input image (sub id = 0)
out_ = node_modifier.addOutput<connection_types::CvMatMessage>("Either image 1 or image 2");
// Connector that demonstrates TutorialMessage
out_tutorial_msg_ = node_modifier.addOutput<connection_types::TutorialMessage>("Tutorial Message (true iff FIRST)");
}
/// register parameters
virtual void setupParameters(Parameterizable& parameters) override
{
/// for demonstration we map an enum to a set parameter of type int
std::map<std::string, int> selector = {
{"FIRST", (int) Selector::FIRST},
{"SECOND", (int) Selector::SECOND},
};
// we register the parameter with the name "selector", the default value of "Selector::FIRST" and a callback lambda function
parameters.addParameter(param::ParameterFactory::declareParameterSet("selector", selector, (int) Selector::FIRST),
[this](param::Parameter* p) {
/// this callback is called whenever the parameter value changes
/// it is also called once before the first call of process
selector_ = static_cast<Selector>(p->as<int>());
});
}
/// callback when all messages arrived
virtual void process(csapex::NodeModifier& node_modifier, csapex::Parameterizable& parameters) override
{
connection_types::CvMatMessage::ConstPtr output_msg;
// selector_ is set whenever the parameter changes
switch(selector_) {
case Selector::FIRST:
output_msg = msg::getMessage<connection_types::CvMatMessage>(in_a_);
break;
case Selector::SECOND:
output_msg = msg::getMessage<connection_types::CvMatMessage>(in_b_);
break;
default:
throw std::runtime_error("unknown selector");
}
msg::publish(out_, output_msg);
// publish the state of selector_ using a TutorialMessage
connection_types::TutorialMessage::Ptr tutorial_msg = std::make_shared<connection_types::TutorialMessage>();
tutorial_msg->value = selector_ == Selector::FIRST;
msg::publish(out_tutorial_msg_, tutorial_msg);
}
private:
Input* in_a_;
Input* in_b_;
Output* out_;
Output* out_tutorial_msg_;
Selector selector_;
};
} // namespace tutorial
} // namespace csapex
// Register the class
CSAPEX_REGISTER_CLASS(csapex::tutorial::UserSwitch, csapex::Node)
Just open the GUI and load the tutorial Tutorials
/ Plug-in Creation
/ 02 - TutorialMessage
to see the message in action.
In the next tutorial we are looking at how to make our custom message type ROS-convertible, so that we can use ROS-I/O.