Skip to content

NewMessage

Sebastian Buck edited this page Aug 15, 2016 · 2 revisions

How to add a new message type?

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?


Primer

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.

Maintenance

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
)

Scaffolding

Fill the newly create files with the following:

Header

#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

Source

/// 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.


Using the new message

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.

Clone this wiki locally