Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modify libYARP_robotinterface and yarprobotinterface to support passing using ${portprefix} in parameters #2819

Merged
merged 3 commits into from
May 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion doc/cmd_yarprobotinterface.dox
Original file line number Diff line number Diff line change
@@ -1,10 +1,43 @@
/**
\page yarprobotinterface yarprobotinterface: start all the devices required by a robot
\page yarprobotinterface yarprobotinterface: Start multiple YARP devices as specified in an xml file.

\ingroup yarp_tools

\tableofcontents

\section yarprobotinterface_intro Description

The yarprobotinterface is a command line tool that is useful to launch multiple YARP device at once.

Its name derives from the fact that the first and main use of the yarprobotinterface was used as the
main program to provide a network "interface", via YARP Network Server Wrappers (NWS) devices, to a robot.

However, the yarprobotinterface can be used to launch YARP devices of any kind. In a sense, is an extension of the
yarpdev command, that instead can be used only to launch one or two devices, while yarprobotinterface can launch an
arbitrary number of YARP devices.

The details of the xml format of the files loaded by yarprobotinterface are documented in \ref yarp_robotinterface_xml_config_files .

\section yarprobotinterface_parameters Parameters

`--config ./configdir/config.xml`
- Specify the path of the `.xml` file to load and that
describes the YARP devices to launch.

`--portprefix portprefix`
- If specified, this values override the portprefix attribute
of the robot element of the xml file.

`--verbose`
- If this option is specified, enable verbose output of the xml parser.

`--dryrun`
- If this option is specified, then xml file is only loaded without actually opening devices.
This option is useful to validate if xml files are well formed.

\section yarprobotinterface_conf_file Configuration Files

yarprobotinterface loads the xml file from the location specified in the `--config` option.


*/
5 changes: 5 additions & 0 deletions doc/release/master/robotinterface_portprefix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
robotinterface_portprefix {#master}
--------------

* Modify libYARP_robotinterface and yarprobotinterface to support
passing using ${portprefix} in parameters (#2819).
49 changes: 49 additions & 0 deletions doc/robotinterface_all.dox
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
\defgroup robointerface_all yarp::robotinterface YARP RobotInterface library

The `libYARP_robotinterface` library is useful to programatically launch YARP devices
from C++ code using the same xml files used with the \ref yarprobotinterface "yarprobotinterface tool",
that are described in \ref yarp_robotinterface_xml_config_files .

An example of use of this library is:
\code
// Load the XML configuration file
std::string pathToXmlConfigurationFile = ...;
yarp::robotinterface::XMLReader reader;
yarp::robotinterface::XMLReaderResult result = reader.getRobotFromFile(pathToXmlConfigurationFile);

if (!result.isParsingSuccessful) {
// Handle error
// ...
}

// Optional: specify externally created devices to which robotinterface's devices can attach
// It is assumed that the devices contained in externalDriverList will remain valid and open until
// result.robot.enterPhase(yarp::robotinterface::ActionPhaseShutdown) will be called
// bool ok = result.robot.setExternalDevices(externalDriverList);

// Enter the startup phase, that will open all the devices and call attach if necessary
bool ok = result.robot.enterPhase(yarp::robotinterface::ActionPhaseStartup);

if (!ok) {
// Handle error
// ...
}


// At this point, the system is running and the thread that called the
// enterPhase methods does not need to do anything else

// This code need to be executed when you want to close the robot
// Close robot, that will close all the devices and call detach if necessary
ok = result.robot.enterPhase(yarp::robotinterface::ActionPhaseInterrupt1);
ok = ok && result.robot.enterPhase(yarp::robotinterface::ActionPhaseShutdown);
if (!ok) {
// Handle error
// ...
}
\endcode


*/

89 changes: 89 additions & 0 deletions doc/yarp_robotinterface_xml_config_files.dox
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
\page yarp_robotinterface_xml_config_files YARP robotinterface XML files

\tableofcontents

This tutorial covers how to write and read XML files that are used by \ref yarprobotinterface "yarprobotinterface tool"
and by the \ref robointerface_all "libYARP_robotinterface C++ library".

\section yarp_robotinterface_xml_config_files_basics A minimal XML file

Here is a minimal config file, let's call it "config.xml":
\code
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE robot PUBLIC "-//YARP//DTD yarprobotinterface 3.0//EN" "http://www.yarp.it/DTD/yarprobotinterfaceV3.0.dtd">

<robot name="robotinterfaceExample" portprefix="/icub" build="0" xmlns:xi="http://www.w3.org/2001/XInclude">
<devices>
<device name="fake_motor_device" type="fakeMotionControl">
<group name="GENERAL">
<!-- Number of joints of the fake_motor_device -->
<param name="Joints"> 3 </param>
</group>
</device>
<device name="fake_motor_nws_yarp" type="controlBoard_nws_yarp">
<!-- See https://www.yarp.it/latest/classControlBoard__nws__yarp.html for parameter documentation -->
<param name="name"> ${portprefix}/body </param>
<param name="period"> 0.01 </param>
<action phase="startup" level="5" type="attach">
<!-- This is the same name that we specified with the name attribute of the device element of a previously created device -->
<param name="device"> fake_motor_device </param>
</action>
<action phase="shutdown" level="5" type="detach" />
</device>
</devices>
</robot>

\endcode

This configuration files create two devices:
* One `fake_motor_device`, that creates a fake motor control board.
* One `fake_motor_nws_yarp`, that creates a Network Wrapper Server (NWS) that exposes `fake_motor_device` functionality over YARP ports.

\section yarp_config_file_reference Reference documentation of XML format.

\subsection robot_element robot Element

The `robot` element is the root element of the XML file. It contains the following attributes:
* `name`: The name of the `robotinterface` instance.
* `portprefix`: The portprefix to be used by the port created by the `robotinterface` instance. It can be used as `${portprefix}` when specifying a parameter. It must start with a `/`.
* `build`: Not used.

\subsection devices_element devices Element

The `devices` element is a child element of `robot` element.

It is a collector of YARP devices that are spawned by the `robotinterface` instance.

\subsection device_element device Element

The `device` element is a child element of `devices` element.

It is used to specify a YARP device that is spawned by the `robotinterface`. It contains the following attributes:
* `name`: The name of the specific instance of YARP device that is created.
* `type`: The name of the type of YARP device to instantiate.

\subsection group_element group Element

The `group` element is a child element of `device` or `action` element.

It is a collector of parameters under a specific group name.

\subsection param_element param Element

The `param` element is a child element of `device`, `action` or `group` element.

This element it is used to specify a specific configuration parameter. It contain the following attributes:
* `name`: The name (i.e. key) of the attribute.

The inner text of the element represents the **value** of the parameter.
If the inner text contains the string ${portprefix}, it will be substituted with the portprefix parameter specified
in the `portprefix` attribute of the `robot` element.


\subsection action_element action Element

This element still needs to be documented.


*/
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
namespace yarp::robotinterface {

/**
* Result of the parsing of XMLReader.
* \ingroup robointerface_all
*
* Result of the parsing of yarp::robotinterface::XMLReader.
*/
class YARP_robotinterface_API XMLReaderResult
{
Expand All @@ -41,6 +43,11 @@ class YARP_robotinterface_API XMLReaderResult
Robot robot;
};

/**
* \ingroup robointerface_all
*
* Class to read an XML file.
*/
class YARP_robotinterface_API XMLReader
{
public:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,15 @@ class yarp::robotinterface::impl::XMLReaderFileV3::Private
yarp::robotinterface::ActionList readActionsTag(TiXmlElement* actionsElem, yarp::robotinterface::XMLReaderResult& result);

bool PerformInclusions(TiXmlNode* pParent, const std::string& parent_fileName, const std::string& current_path);

void ReplaceAllStrings(std::string& str, const std::string& from, const std::string& to);
XMLReaderFileV3* const parent;

#ifdef USE_DTD
RobotInterfaceDTD dtd;
#endif

bool verbose_output;
const yarp::os::Searchable* config;
yarp::os::Property config;
std::string curr_filename;
unsigned int minorVersion;
unsigned int majorVersion;
Expand Down Expand Up @@ -252,9 +252,16 @@ yarp::robotinterface::XMLReaderResult yarp::robotinterface::impl::XMLReaderFileV
result.robot.build() = static_cast<unsigned>(tmp);
#endif

if (robotElem->QueryStringAttribute("portprefix", &result.robot.portprefix()) != TIXML_SUCCESS) {
SYNTAX_WARNING(robotElem->Row()) << R"("robot" element should contain the "portprefix" attribute. Using "name" attribute)";
result.robot.portprefix() = result.robot.name();
// If portprefix is already present in config we use that one
if (!config.check("portprefix"))
{
if (robotElem->QueryStringAttribute("portprefix", &result.robot.portprefix()) != TIXML_SUCCESS) {
SYNTAX_WARNING(robotElem->Row()) << R"("robot" element should contain the "portprefix" attribute. Using "name" attribute)";
result.robot.portprefix() = result.robot.name();
}
config.put("portprefix",result.robot.portprefix());
} else {
result.robot.portprefix() = config.find("portprefix").asString();
}

// FIXME DTD >= 4 Make this the default behaviour
Expand Down Expand Up @@ -410,6 +417,17 @@ yarp::robotinterface::ParamList yarp::robotinterface::impl::XMLReaderFileV3::Pri
return yarp::robotinterface::ParamList();
}

void yarp::robotinterface::impl::XMLReaderFileV3::Private::ReplaceAllStrings(std::string& str, const std::string& from, const std::string& to)
{
if(from.empty())
return;
size_t start_pos = 0;
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
str.replace(start_pos, from.length(), to);
start_pos += to.length();
}
}


yarp::robotinterface::Param yarp::robotinterface::impl::XMLReaderFileV3::Private::readParamTag(TiXmlElement* paramElem,
yarp::robotinterface::XMLReaderResult& result)
Expand Down Expand Up @@ -437,14 +455,24 @@ yarp::robotinterface::Param yarp::robotinterface::impl::XMLReaderFileV3::Private
return yarp::robotinterface::Param();
}

// First process extern-name
std::string extern_name;
if (paramElem->QueryStringAttribute("extern-name", &extern_name) == TIXML_SUCCESS && config && config->check(extern_name)) {
if (paramElem->QueryStringAttribute("extern-name", &extern_name) == TIXML_SUCCESS && config.check(extern_name)) {
// FIXME Check DTD >= 3.1
param.value() = config->find(extern_name).toString();
param.value() = config.find(extern_name).toString();
} else {
param.value() = valueText;
}

// After process ${portprefix}
std::string paramValueBefore = param.value();
std::string paramValueAfter = paramValueBefore;
std::string portprefix = config.find("portprefix").toString();
ReplaceAllStrings(paramValueAfter, "${portprefix}", portprefix);
param.value() = paramValueAfter;



// yDebug() << param;
return param;
}
Expand Down Expand Up @@ -732,21 +760,21 @@ yarp::robotinterface::XMLReaderResult yarp::robotinterface::impl::XMLReaderFileV
const yarp::os::Searchable& config,
bool verb)
{
mPriv->config = &config;
mPriv->config.fromString(config.toString());
mPriv->verbose_output = verb;
auto ret = mPriv->readRobotFromFile(filename);
mPriv->config = nullptr;
mPriv->config.clear();
return ret;
}

yarp::robotinterface::XMLReaderResult yarp::robotinterface::impl::XMLReaderFileV3::getRobotFromString(const std::string& xmlString,
const yarp::os::Searchable& config,
bool verb)
{
mPriv->config = &config;
mPriv->config.fromString(config.toString());
mPriv->verbose_output = verb;
auto ret = mPriv->readRobotFromString(xmlString);
mPriv->config = nullptr;
mPriv->config.clear();
return ret;
}

Expand Down
14 changes: 13 additions & 1 deletion src/yarprobotinterface/Module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,19 @@ bool yarprobotinterface::Module::configure(yarp::os::ResourceFinder& rf)
mPriv->robot.setAllowDeprecatedDevices(rf.check("allow-deprecated-devices"));
mPriv->robot.setDryRun(dryrun);

std::string rpcPortName("/" + getName() + "/yarprobotinterface");
std::string portprefix = mPriv->robot.portprefix();
if (portprefix[0] != '/') {
yWarning() <<
"*************************************************************************************\n"
"* yarprobotinterface 'portprefix' parameter does not follow convention, *\n"
"* it MUST start with a leading '/' since it is used as the full prefix port name *\n"
"* name: full port prefix name with leading '/', e.g. /robotName *\n"
"* A temporary automatic fix will be done for you, but please fix your config file *\n"
"*************************************************************************************";
portprefix = "/" + portprefix;
}

std::string rpcPortName(portprefix + "/yarprobotinterface");
mPriv->rpcPort.open(rpcPortName);
attach(mPriv->rpcPort);

Expand Down
Loading