Skip to content

Commit

Permalink
Added ros::package wrapper.
Browse files Browse the repository at this point in the history
  • Loading branch information
StefanFabian committed Apr 17, 2020
1 parent ba03a61 commit 1b7c015
Show file tree
Hide file tree
Showing 14 changed files with 332 additions and 5 deletions.
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ set(SOURCES
include/qml_ros_plugin/image_transport_subscriber.h
include/qml_ros_plugin/message_conversions.h
include/qml_ros_plugin/node_handle.h
include/qml_ros_plugin/package.h
include/qml_ros_plugin/publisher.h
include/qml_ros_plugin/qml_ros_conversion.h
include/qml_ros_plugin/qobject_ros.h
Expand All @@ -68,6 +69,7 @@ set(SOURCES
src/image_transport_subscriber.cpp
src/message_conversions.cpp
src/node_handle.cpp
src/package.cpp
src/publisher.cpp
src/qml_ros_plugin.cpp
src/qobject_ros.cpp
Expand Down Expand Up @@ -121,6 +123,10 @@ if (CATKIN_ENABLE_TESTING)
add_rostest_gtest(${PROJECT_NAME}_test_logging test/test_logging.test test/logging.cpp)
target_link_libraries(${PROJECT_NAME}_test_logging ${PROJECT_NAME})
set_target_properties(${PROJECT_NAME}_test_logging PROPERTIES OUTPUT_NAME test_logging PREFIX "")

add_rostest_gtest(${PROJECT_NAME}_test_package test/test_package.test test/package.cpp)
target_link_libraries(${PROJECT_NAME}_test_package ${PROJECT_NAME})
set_target_properties(${PROJECT_NAME}_test_package PROPERTIES OUTPUT_NAME test_package PREFIX "")
endif ()

# to run: catkin build --this --no-deps -DENABLE_COVERAGE_TESTING=ON -DCMAKE_BUILD_TYPE=Debug -v --catkin-make-args qml_ros_plugin_coverage
Expand Down
16 changes: 16 additions & 0 deletions docs/pages/Ros-Singleton.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,24 @@ Example:
if (Ros.queryTopicType(topics[i]) == "sensor_msgs/Image") cameraTopics.push(topics[i])
}
Package API
-----------
The package property provides a wrapper for ``ros::package``.

.. code-block:: qml
// Retrieve a list of all packages
var packages = Ros.package.getAll()
// Get the fully-qualified path to a specific package
var path = Ros.package.getPath("some_pkg")
// Get plugins for a package as a map [package_name -> [values]]
var plugins = Ros.package.getPlugins("rviz", "plugin")
API
---
.. doxygenclass:: qml_ros_plugin::Package
:members:

.. doxygenclass:: qml_ros_plugin::TopicInfo
:members:

Expand Down
63 changes: 63 additions & 0 deletions include/qml_ros_plugin/package.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) 2020 Stefan Fabian. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#ifndef QML_ROS_PLUGIN_PACKAGE_H
#define QML_ROS_PLUGIN_PACKAGE_H

#include <QtCore>

namespace qml_ros_plugin
{

/*!
* A wrapper for ros::package
*/
class Package
{
Q_GADGET

public:
/*!
* Runs a command of the form 'rospack <cmd>'. (This does not make a subprocess call!)
* @param cmd The command passed to rospack.
* @return The output of the command as a string.
*/
Q_INVOKABLE QString command( const QString &cmd );

/*!
* Queries the path to a package.
* @param package_name The name of the package.
* @return The fully-qualified path to the given package or an empty string if the package is not found.
*/
Q_INVOKABLE QString getPath( const QString &package_name );

/*!
* @return A list of all packages.
*/
Q_INVOKABLE QStringList getAll();

/*!
* Queries for all plugins exported for a given package.
*
* @code{.xml}
* <export>
* <name attribute="value"/>
* <rviz plugin="${prefix}/plugin_description.xml"/>
* </export>
* @endcode
*
* To query for rviz plugins you would pass 'rviz' as the name and 'plugin' as the attribute.
*
* @param name The name of the export tag.
* @param attribute The name of the attribute for the value is obtained.
* @param force_recrawl Forces rospack to rediscover everything on the system before running the search.
* @return A map with the name of the package exporting something for name as the key (string) and a QStringList
* containing all exported values as the value.
*/
Q_INVOKABLE QVariantMap getPlugins( const QString &name, const QString &attribute, bool force_recrawl = false );
};
}

Q_DECLARE_METATYPE( qml_ros_plugin::Package );

#endif //QML_ROS_PLUGIN_PACKAGE_H
6 changes: 6 additions & 0 deletions include/qml_ros_plugin/ros.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "qml_ros_plugin/console.h"
#include "qml_ros_plugin/node_handle.h"
#include "qml_ros_plugin/package.h"
#include "qml_ros_plugin/topic_info.h"

#include <QJSValue>
Expand Down Expand Up @@ -119,6 +120,8 @@ Q_OBJECT

Console console() const;

Package package() const;

/*!
* A callback queue that is guaranteed to be called on a background thread.
* @return A shared pointer to the callback queue.
Expand Down Expand Up @@ -157,6 +160,7 @@ Q_OBJECT

// @formatter:off
Q_PROPERTY( qml_ros_plugin::Console console READ console )
Q_PROPERTY( qml_ros_plugin::Package package READ package )
Q_PROPERTY( QJSValue debug READ debug )
Q_PROPERTY( QJSValue info READ info )
Q_PROPERTY( QJSValue warn READ warn )
Expand Down Expand Up @@ -194,6 +198,8 @@ Q_OBJECT

Console console() const;

Package package() const;

/*!
* Outputs a ROS debug message. The equivalent of calling ROS_DEBUG in C++.
* The signature in QML is @c debug(msg, consoleName) where @a consoleName is an optional parameter which defaults to
Expand Down
56 changes: 56 additions & 0 deletions src/package.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) 2020 Stefan Fabian. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#include "qml_ros_plugin/package.h"
#include "qml_ros_plugin/message_conversions.h"

#include <ros/package.h>
#include <ros/ros.h>

namespace qml_ros_plugin
{

QString Package::command( const QString &cmd )
{
return QString::fromStdString( ros::package::command( cmd.toStdString()));
}

QString Package::getPath( const QString &package_name )
{
return QString::fromStdString( ros::package::getPath( package_name.toStdString()));
}

QStringList Package::getAll()
{
QStringList result;
ros::V_string result_std;
if ( !ros::package::getAll( result_std ))
{
ROS_WARN_NAMED( "qml_ros_plugin", "Failed to get packages!" );
return {};
}
result.reserve( result_std.size());
for ( const auto &s : result_std )
result.append( QString::fromStdString( s ));
return result;
}

QVariantMap Package::getPlugins( const QString &name, const QString &attribute, bool force_recrawl )
{
std::vector<std::pair<std::string, std::string>> exports;
ros::package::getPlugins( name.toStdString(), attribute.toStdString(), exports, force_recrawl );
QVariantMap result;
for ( const auto &pair : exports )
{
const QString &package = QString::fromStdString( pair.first );
const QString &value = QString::fromStdString( pair.second );
if ( result.contains( package ))
{
conversion::obtainValueAsReference<QStringList>( result[package] ).append( value );
continue;
}
result.insert( package, QStringList{ value } );
}
return result;
}
}
3 changes: 2 additions & 1 deletion src/qml_ros_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@ Q_OBJECT
qRegisterMetaType<actionlib::ActionClient<ros_babel_fish::BabelFishAction>::GoalHandle>();

qmlRegisterType<Array>();
qmlRegisterType<Console>();
qmlRegisterType<Package>();
qmlRegisterUncreatableMetaObject( ros_init_options::staticMetaObject, "Ros", 1, 0, "RosInitOptions",
"Error: Can not create enum object." );
qmlRegisterUncreatableMetaObject( ros_console_levels::staticMetaObject, "Ros", 1, 0, "RosConsoleLevels",
"Error: Can not create enum object." );
qmlRegisterType<Console>();
qmlRegisterSingletonType<RosQmlSingletonWrapper>( "Ros", 1, 0, "Ros",
[]( QQmlEngine *engine, QJSEngine *scriptEngine ) -> QObject *
{
Expand Down
9 changes: 5 additions & 4 deletions src/ros.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,9 @@ void RosQml::updateSpinner()
spinner_->start();
}

Console RosQml::console() const
{
return {};
}
Console RosQml::console() const { return {}; }

Package RosQml::package() const { return {}; }

std::shared_ptr<ros::CallbackQueue> RosQml::callbackQueue()
{
Expand Down Expand Up @@ -212,6 +211,8 @@ QString RosQmlSingletonWrapper::queryTopicType( const QString &name ) const

Console RosQmlSingletonWrapper::console() const { return RosQml::getInstance().console(); }

Package RosQmlSingletonWrapper::package() const { return RosQml::getInstance().package(); }

QJSValue RosQmlSingletonWrapper::debug()
{
if ( debug_function_.isCallable()) return debug_function_;
Expand Down
1 change: 1 addition & 0 deletions test/all_tests.test
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<include file="$(find qml_ros_plugin)/test/test_image_transport_subscriber.test"/>
<include file="$(find qml_ros_plugin)/test/test_logging.test"/>
<include file="$(find qml_ros_plugin)/test/test_message_conversions.test"/>
<include file="$(find qml_ros_plugin)/test/test_package.test"/>
<include file="$(find qml_ros_plugin)/test/test_ros_life_cycle.test"/>
<include file="$(find qml_ros_plugin)/test/test_spinning.test"/>
</launch>
103 changes: 103 additions & 0 deletions test/package.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//
// Created by Stefan Fabian on 04.03.20.
//

#include "common.h"
#include "message_comparison.h"
//#ifndef _WIN32
//#include <unistd.h>
//#endif

#include <qml_ros_plugin/ros.h>

#include <ros/ros.h>
#include <ros/package.h>

#ifdef _WIN32
int setenv(const char *name, const char *value, int overwrite)
{
if(!overwrite)
{
size_t envsize = 0;
errno_t errcode = getenv_s(&envsize, NULL, 0, name);
if(errcode || envsize)
return errcode;
}
return _putenv_s(name, value);
}
#endif

template<typename ContainerA, typename KeyType>
::testing::AssertionResult listEquals( const ContainerA &container, const std::vector<KeyType> &values )
{
std::map<KeyType, bool> found;
for ( const auto &key : values )
{
found.insert( { key, false } );
}
for ( const auto &value : container )
{
if ( found.find( value ) == found.end())
return ::testing::AssertionFailure() << value << " is in container but was not expected!";
found[value] = true;
}
for ( const auto &pair : found )
{
if ( !pair.second )
return ::testing::AssertionFailure() << pair.first << " was not found in container.";
}
return ::testing::AssertionSuccess();
}

using namespace qml_ros_plugin;

TEST( Package, package )
{
RosQmlSingletonWrapper wrapper;
QString path = wrapper.package().getPath( ROS_PACKAGE_NAME );
ASSERT_FALSE( path.isEmpty());
EXPECT_EQ( path.toStdString(), ros::package::getPath( ROS_PACKAGE_NAME ));

char *oldrpp = getenv( "ROS_PACKAGE_PATH" );
std::string package_path = path.toStdString() + "/test/test_packages";
setenv( "ROS_PACKAGE_PATH", package_path.c_str(), 1 );
path = wrapper.package().getPath( ROS_PACKAGE_NAME );
EXPECT_TRUE( path.isEmpty());

EXPECT_TRUE( listEquals( wrapper.package().getAll(),
std::vector<QString>{ "poor_people", "rich_people", "rick_astley", "world" } ));

QVariantMap plugins = wrapper.package().getPlugins( "world", "value" );
std::map<QString, std::vector<QString>> expected_plugins = {{ "rich_people", { "Nothing but despair" }},
{ "rick_astley", { "Never Gonna Give You Up" }}};
for ( const auto &key : plugins.keys())
{
ASSERT_NE( expected_plugins.find( key ), expected_plugins.end()) << key;
EXPECT_TRUE( listEquals( plugins[key].toStringList(), expected_plugins[key] )) << key;
}

plugins = wrapper.package().getPlugins( "world", "wars" );
expected_plugins = {{ "rich_people", { "true" }}};

for ( const auto &key : plugins.keys())
{
ASSERT_NE( expected_plugins.find( key ), expected_plugins.end()) << key;
EXPECT_TRUE( listEquals( plugins[key].toStringList(), expected_plugins[key] )) << key;
}

QString output = wrapper.package().command( "depends-on poor_people" );
EXPECT_EQ( output.trimmed(), QString( "rich_people" ));

setenv( "ROS_PACKAGE_PATH", (package_path + "/not_existing_subfolder").c_str(), 1 );
ASSERT_TRUE( wrapper.package().getAll().empty());

setenv( "ROS_PACKAGE_PATH", oldrpp, 1 );
}

int main( int argc, char **argv )
{
testing::InitGoogleTest( &argc, argv );
ros::init( argc, argv, "test_logging" );
QCoreApplication app( argc, argv );
return RUN_ALL_TESTS();
}
3 changes: 3 additions & 0 deletions test/test_package.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<launch>
<test test-name="package" pkg="qml_ros_plugin" type="test_package"/>
</launch>
17 changes: 17 additions & 0 deletions test/test_packages/poor_people/package.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<package format="2">
<name>poor_people</name>
<version>1.0.0</version>
<description>The poor_people package</description>

<maintainer email="qml_ros_plugin@stefanfabian.me">Stefan Fabian</maintainer>

<license>Birthright</license>

<buildtool_depend>catkin</buildtool_depend>

<depend>world</depend>

<export>
</export>
</package>

0 comments on commit 1b7c015

Please sign in to comment.