In this tutorial we shall see how to develop and run your own Service. Our example will show how to buld a HelloService
which does nothing besides printing "Hello MeRMaID::support"
every second.
Please make sure you have correctly installed MeRMaID::support
.
Don't be afraid to do this because of the length of this tutorial: it is just very verbose in everything it tells you to do. ;-)
Copy the files from the dummy example to your working directory and rename the DummyService
.[ch]pp and dummy-service-configuration-file.xml
files to something more suited to the service you wish to develop.
The DummyService can be found in mermaid-support/Example/DummyService
.
We will use HelloService as the service's name.
At this point you should have the following files in your working directory:
CMakeLists.txt
data-description-file.xml
deployment-configuration.xml
entity-description-file.xml
hello-service-configuration-file.xml
(original name was dummy-service-configuration-file.xml
)HelloService.cpp
(original name was DummyService.cpp
)HelloService.hpp
(original name was DummyService.hpp
)service-type-description.xml
Open the HelloService.hpp
file for editing. Change the include guards. It is recommended for the include guard to reflect the path of the include file within your project. For instance, if HelloService.hpp
was inside components/helloService
within your project's folder, then your include guard could be:
#ifndef COMPONENTS_HELLOSERVICE_HELLOSERVICE_HPP #define COMPONENTS_HELLOSERVICE_HELLOSERVICE_HPP \/* ... rest of the header file ... *\/ #endif // COMPONENTS_HELLOSERVICE_HELLOSERVICE_HPP
Change the used namespace. We will also use the header file's path as a way to build the service's namespace. Therefore, we will have this class in the following namespace:
namespace components { namespace helloservice { ... rest of class declaration ... }; //namespace helloservice }; //namespace components
Change the class name. Our chosen class name for this service is HelloService
The class declaration should look like this:
class HelloService : public Service {
Also change the class's constructor name (leave all of its parameters the same):
HelloService(CountedPtr<ActiveObject> ao, CountedPtr<Entity> entity, CountedPtr<ServiceInstanceDescription> serviceInstanceDescription, CountedPtr<ServiceConfiguration> serviceConfiguration);
Your HelloService.hpp
file should look like this:
#ifndef COMPONENTS_HELLOSERVICE_HELLOSERVICE_HPP #define COMPONENTS_HELLOSERVICE_HELLOSERVICE_HPP #include <ActiveObject.hpp> #include <CountedPtr.hpp> #include <Entity.hpp> #include <EntityDescription.hpp> #include <ServiceConfiguration.hpp> #include <Service.hpp> #include <ServiceInstanceDescription.hpp> #include <ServiceInterfaceDescription.hpp> #include <ServiceReply.hpp> #include <ServiceRequest.hpp> #include <XmlDocument.hpp> namespace components { namespace helloservice { using mermaid::support::activeobject::ActiveObject; using mermaid::support::memorymanagement::CountedPtr; using mermaid::support::service::Entity; using mermaid::support::service::EntityDescription; using mermaid::support::service::ServiceConfiguration; using mermaid::support::service::Service; using mermaid::support::service::ServiceInstanceDescription; using mermaid::support::service::ServiceReply; using mermaid::support::service::ServiceRequest; using mermaid::support::xml::XmlDocument; class HelloService : public Service { public: HelloService(CountedPtr<ActiveObject> ao, CountedPtr<Entity> entity, CountedPtr<ServiceInstanceDescription> serviceInstanceDescription, CountedPtr<ServiceConfiguration> serviceConfiguration); virtual void initialize(); virtual void update(); private: }; // class DummyService }; // namespace helloservice }; // namespace dummy #endif // COMPONENTS_HELLOSERVICE_HELLOSERVICE_HPP
Change the include statement to the new header file name:
#include "HelloService.hpp"
Change the namespace to the one in which the class was declared:
using namespace components::helloservice;
Wherever DummyService appears, replace it with your class name (in this case HelloService
)
We want our Service
to print the message "Hello MeRMaID::support" every second. In order to do this, we will write the print code in the update()
method. This method is called at a fixed frequency which is set in the Service's configuration file . Therefore the update()
method should look like this: void HelloService::update() { std::cout << "Hello MeRMaID::support" << std::endl; }; // update
Your HelloService.cpp
file should look like this:
#include "HelloService.hpp" #include <iostream> using namespace components::helloservice; HelloService::HelloService(CountedPtr<ActiveObject> ao, CountedPtr<Entity> entity, CountedPtr<ServiceInstanceDescription> serviceInstanceDescription, CountedPtr<ServiceConfiguration> serviceConfiguration) : Service(ao, entity, serviceInstanceDescription, serviceConfiguration) { // nothing else should be done here }; // HelloService() void HelloService::initialize() { std::cout << "HelloService::initialize()" << std::endl; }; // initialize void HelloService::update() { std::cout << "Hello MeRMaID::support" << std::endl; }; // update
This file is used to describe the type of service we're creating. This is kind of a description of the service's class itself. You give the service type a name (typically the same name as its class name) and write a small description for it. In our example the file looks like this:
<?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE service-type-description-list SYSTEM "service-type-description-list.dtd"> <service-type-description-list> <service-type-description> <name>HelloService</name> <comment>Service that prints a hello message.</comment> </service-type-description> </service-type-description-list>
This file describes the existing entities and which services are running in them. An entity is seen as a robot or a stand-alone computer (or an array of computers that, from a system point of view, can be seen as one unit). It is important to use one entity name per robot/computer since this information may be used by MeRMaID::support
to do communication routing. Declaring several services as belonging to the same entity but having them spread troughout more than one robot/computer may have nasty consequences.
In our example, we intend to use the HelloService service on a stand-alone computer which we call "MyComputer". Besides the entity name, we have to declare names for each service instance we want to have in the system. Also, each service instance should be of a service type that has been previously specified in the service type description file. In our case, we want an instance of the HelloService service type which we will call "helloService". There should also be a comment stating what is each service instance intended to do.
Having all this defined for our example, the file should look like this:
<?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE entity-description-list SYSTEM "entity-description-list.dtd"> <entity-description-list> <entity> <name>MyComputer</name> <service> <instance-name>helloService</instance-name> <type-name>HelloService</type-name> <comment>HelloService instance</comment> </service> </entity> </entity-description-list>
The deployment-configuration.xml is used to describe how services are to be deployed. This information is used by mlgen
(MeRMaID Loader Generator) to automatically create executables that deploy services. This file has several parts that are described in the following subsections:
The file-search-path
item specifies were should MeRMaID::support
look for files (such as those xml files we just changed earlier). There should be at least two entries: MeRMaID::support's
config directory and the directory containing the users configuration files. There can be as much file-search-path
entries as needed. In this example case we will leave it with the default parameters:
<file-search-path>/opt/mermaid/config</file-search-path> <file-search-path>./</file-search-path>
The following entries state the name for the three description files used by MeRMaID::support:
data-description-file
, service-type-description-file
and entity-description-file
. These values should be changed from the defaults in case you changed the names for the description files that were edited earlier. In our example, we'll stick with the defaults:
<data-description-file>data-description-file.xml</data-description-file> <service-type-description-file>service-type-description-file.xml</service-type-description-file> <entity-description-file>entity-description-file.xml</entity-description-file>
Here we declare all the ActiveObjects that are to be deployed. An ActiveObject is who actually runs the Service (in fact, ActiveObject's run Task's that are scheduled by Services, but those are details). An ActiveObject is a means by which to ensure mutual exclusion between "pieces of code". Typically, each Service runs in its own ActiveObject and execution of any of the Service's methods are guaranteed to not run concurently. If two Service's need to run in mutual exclusion they may be set to run inside the same ActiveObject, but be cautious with this solution since it may lead to a big performance penalty.
For our example, we only want to run one Service (for now) and it should run in its own ActiveObject. Therefore, we need to state the existence of an ActiveObject in which it will run. We will name it "helloActiveObject". This section will look like this:
<active-object> <active-object-name>helloActiveObject</active-object-name> </active-object>
The service
section declares a Service. There should be one service
section for each Service that is intended to be deployed. The following information is needed for each Service (the correct values for this example is stated between parentheses :
header-file
: name of the header file of the Service's class (HelloService.hpp
)namespace
: the namespace in which the Service is declared (components::helloservice
)class-name
: the Service's class name (HelloService
)entity-name
: the name of the Entity in which the Service is going to be run (MyComputer
)instance-name
: the name of the Service's instance whithin the specified entity (helloService
)configuration-file
: the Service's configuration file (hello-service-configuration-file.xml
)active-object-name
: the ActiveObject in which the Service should be run. This should correspond to one of the ActiveObject's declared earlier (helloActiveObject
)After all these changes, the deployment-configuration.xml file should look like this:
<?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE deployment-configuration SYSTEM "deployment-configuration.dtd"> <deployment-configuration> <file-search-path>/opt/mermaid/config</file-search-path> <file-search-path>./</file-search-path> <data-description-file>data-description-file.xml</data-description-file> <service-type-description-file>service-type-description-file.xml</service-type-description-file> <entity-description-file>entity-description-file.xml</entity-description-file> <active-object> <active-object-name>helloActiveObject</active-object-name> </active-object> <service> <header-file>HelloService.hpp</header-file> <namespace>components::helloservice</namespace> <class-name>HelloService</class-name> <entity-name>MyComputer</entity-name> <instance-name>helloService</instance-name> <configuration-file>hello-service-configuration-file.xml</configuration-file> <active-object-name>helloActiveObject</active-object-name> </service> </deployment-configuration>
In the configuration file we will specify how many times per second we want our Service
's update
method to be called. This is defined in the update-frequency
field. Since we want our Service
to update once per second this field should have the value "1"
.
The configuration file should look like this:
<?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE service-configuration SYSTEM "service-configuration.dtd"> <service-configuration> <update-frequency>1</update-frequency> </service-configuration>
MeRMaID::support
uses cmake
( www.cmake.org ) to generate Makefiles and to manage the build process. We recommend to use cmake for projects using MeRMaID::support
too as it simplifies your life. As a last step in order to have the Service running, you should edit the CMakeLists.txt
file to reflect the new name of our Service class. A name for the generated executable should also be chosen. Here, we'll name the executable as "runHelloService"
. The CMakeLists.txt
file should look like this:
FIND_PACKAGE(YARP) FIND_PACKAGE(MERMAID) MLGEN("deployment-configuration.xml" runHelloService.cpp) ADD_EXECUTABLE(runHelloService runHelloService.cpp HelloService.cpp)
Since we use cmake
, building the service is just a matter of:
cmake . make
If you get complaints like these:
CMake Error: YARP_DIR is not set. It must be set to the directory containing YARPConfig.cmake in order to use YARP. CMake Error: MERMAID_DIR is not set. It must be set to the directory containing MERMAIDConfig.cmake in order to use MERMAID.
You should set your YARP_DIR environment variable to point to where you have YARP and your MERMAID_DIR environment variable to where you installed MeRMaID::support
(typically /opt/mermaid
)
In order to run your service you should first make sure that you have a YARP server running. If you don't have a yarp server running, you may start one yourself:
yarp server
After that, you just need to run the executable which has the name you set for the executable in the CMakeLists.txt file. In our case, the executable is named runDrivingJoystick
so you should do the following:
./runHelloService
By running the service you should see some output written by YARP followed by:
HelloService::initialize() Hello MeRMaID::support Hello MeRMaID::support Hello MeRMaID::support Hello MeRMaID::support
The first line corresponds to the execution of the HelloService::initialize()
method, and each of the repeating lines with "Hello MeRMaID::support" correspond to each execution of the HelloService::update()
method. The rate at which this method is called is defined in the hello-service-configuration-file.xml
The full code for this tutorial is available in the HelloService
example Service
.