Skip to content

Commit

Permalink
Added topic query methods.
Browse files Browse the repository at this point in the history
  • Loading branch information
StefanFabian committed Mar 12, 2020
1 parent 6ae4fa2 commit 3eb86e0
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 3 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ set(SOURCES
include/qml_ros_plugin/subscriber.h
include/qml_ros_plugin/tf_transform.h
include/qml_ros_plugin/tf_transform_listener.h
include/qml_ros_plugin/topic_info.h
include/qml_ros_plugin/time.h
src/array.cpp
src/action_client.cpp
Expand Down
39 changes: 38 additions & 1 deletion docs/pages/Ros-Singleton.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,52 @@ following ``RosInitOptions`` options:
* | ``AnonymousName``:
| Anonymize the node name. Adds a random number to the node's name to make it unique.
* | ``NoRosout``:
| Don't broadcast rosconsole output to the /rosout topic. See :ref:`Logging`
| Don't broadcast rosconsole output to the /rosout topic. See :doc:`Logging`
.. code-block:: qml
Component.onCompleted: {
Ros.init("node_name", RosInitOptions.AnonymousName | RosInitOptions.NoRosout)
}
Query Topics
------------

You can also use the Ros singleton to query the available topics.
Currently, three methods are provided:

* | ``QStringList queryTopics( const QString &datatype = QString())``
| Queries a list of topics with the given datatype or all topics if no type provided.
* | ``QList<TopicInfo> queryTopicInfo()``
| Retrieves a list of all advertised topics including their datatype. See :cpp:class:`TopicInfo`
* | ``QString queryTopicType( const QString &name )``
| Reterieves the datatype for a given topic.
Example:

.. code-block:: qml
// Retrieves a list of topics with the type sensor_msgs/Image
var topics = Ros.queryTopics("sensor_msgs/Image")
// Another slower and less clean method of this would be
var cameraTopics = []
var topics = Ros.queryTopicInfo()
for (var i = 0; i < topics.length; ++i) {
if (topics[i].datatype == "sensor_msgs/Image") cameraTopics.push(topics[i].name)
}
// The type of a specific topic can be retrieved as follows
var datatype = Ros.queryTopicType("/topic/that/i/care/about")
// Using this we can make an even worse implementation of the same functionality
var cameraTopics = []
var topics = Ros.queryTopics() // Gets all topics
for (var i = 0; i < topics.length; ++i) {
if (Ros.queryTopicType(topics[i]) == "sensor_msgs/Image") cameraTopics.push(topics[i])
}
API
---
.. doxygenclass:: qml_ros_plugin::TopicInfo
:members:

.. doxygenclass:: qml_ros_plugin::RosQmlSingletonWrapper
:members:
2 changes: 0 additions & 2 deletions include/qml_ros_plugin/goal_handle.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
#include <ros_babel_fish/actionlib/babel_fish_action.h>
#include <ros_babel_fish/babel_fish.h>

#include <utility>

namespace qml_ros_plugin
{

Expand Down
31 changes: 31 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/topic_info.h"

#include <QJSValue>
#include <QObject>
Expand Down Expand Up @@ -95,6 +96,27 @@ Q_OBJECT
*/
void setThreads( int count );

/*!
* Queries the ROS master for its topics or using the optional datatype parameter for all topics with the given type.
* @param datatype The message type to filter topics for, e.g., sensor_msgs/Image. Omit to query for all topics.
* @return A list of topics that matches the given datatype or all topics if no datatype provided.
*/
QStringList queryTopics( const QString &datatype = QString()) const;

/*!
* Queries the ROS master for its topics and their type.
* @return A list of TopicInfo.
*/
QList<TopicInfo> queryTopicInfo() const;

/*!
* Queries the ROS master for a topic with the given name.
* @param name The name of the topic, e.g., /front_camera/image_raw.
* @return The type of the topic if found, otherwise an empty string.
*/
QString queryTopicType( const QString &name ) const;


Console console() const;

signals:
Expand Down Expand Up @@ -154,6 +176,15 @@ Q_OBJECT
//! @copydoc RosQml::setThreads
Q_INVOKABLE void setThreads( int count );

//! @copydoc RosQml::queryTopics
Q_INVOKABLE QStringList queryTopics( const QString &datatype = QString()) const;

//! @copydoc RosQml::queryTopicInfo
Q_INVOKABLE QList<TopicInfo> queryTopicInfo() const;

//! @copydoc RosQml::queryTopicType
Q_INVOKABLE QString queryTopicType( const QString &name ) const;

Console console() const;

/*!
Expand Down
40 changes: 40 additions & 0 deletions include/qml_ros_plugin/topic_info.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// 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_TOPIC_INFO_H
#define QML_ROS_PLUGIN_TOPIC_INFO_H

#include <QArgument>
#include <QMetaType>
#include <QString>

namespace qml_ros_plugin
{

class TopicInfo
{
Q_GADGET
// @formatter:off
//! The name of the topic, e.g., /front_camera/image_raw
Q_PROPERTY( QString name READ name )
//! The datatype of the topic, e.g., sensor_msgs/Image
Q_PROPERTY( QString datatype READ datatype )
// @formatter:on
public:
TopicInfo() = default;

TopicInfo( QString name, QString datatype ) : name_( std::move( name )), datatype_( std::move( datatype )) { }

const QString &name() const { return name_; }

const QString &datatype() const { return datatype_; }

private:
QString name_;
QString datatype_;
};
}

Q_DECLARE_METATYPE( qml_ros_plugin::TopicInfo );

#endif //QML_ROS_PLUGIN_TOPIC_INFO_H
2 changes: 2 additions & 0 deletions src/qml_ros_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ Q_OBJECT
} );
qmlRegisterUncreatableType<NodeHandle>( "Ros", 1, 0, "NodeHandle",
"Error: Can not create NodeHandle manually in QML." );
qmlRegisterUncreatableType<TopicInfo>( "Ros", 1, 0, "TopicInfo",
"Error: No point in creating TopicInfo in QML and it's not supported." );
qmlRegisterUncreatableType<Publisher>( "Ros", 1, 0, "Publisher",
"Error: Can not create Publisher manually in QML. Use one of the advertise functions." );

Expand Down
54 changes: 54 additions & 0 deletions src/ros.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "qml_ros_plugin/publisher.h"
#include "qml_ros_plugin/subscriber.h"

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

#include <QCoreApplication>
Expand Down Expand Up @@ -73,6 +74,47 @@ void RosQml::setThreads( int count )
updateSpinner();
}

QStringList RosQml::queryTopics( const QString &datatype ) const
{
ros::master::V_TopicInfo topic_info;
ros::master::getTopics( topic_info );
QStringList result;
std::string std_datatype = datatype.toStdString();
for ( const auto &topic : topic_info )
{
if ( !std_datatype.empty() && topic.datatype != std_datatype ) continue;
result.append( QString::fromStdString( topic.name ));
}
return result;
}

QList<TopicInfo> RosQml::queryTopicInfo() const
{
ros::master::V_TopicInfo topic_info;
ros::master::getTopics( topic_info );
QList<TopicInfo> result;
for ( const auto &topic : topic_info )
{
result.append( { QString::fromStdString( topic.name ), QString::fromStdString( topic.datatype ) } );
}
return result;
}

QString RosQml::queryTopicType( const QString &name ) const
{
if ( name.isEmpty()) return {};
ros::master::V_TopicInfo topic_info;
ros::master::getTopics( topic_info );
QList<TopicInfo> result;
std::string std_name = name.toStdString();
for ( const auto &topic : topic_info )
{
if ( std_name != topic.name ) continue;
return QString::fromStdString( topic.datatype );
}
return {};
}

void RosQml::checkInitialized()
{
if ( !ros::isInitialized()) return;
Expand Down Expand Up @@ -154,6 +196,18 @@ void RosQmlSingletonWrapper::spinOnce() { RosQml::getInstance().spinOnce(); }

void RosQmlSingletonWrapper::setThreads( int count ) { RosQml::getInstance().setThreads( count ); }

QStringList RosQmlSingletonWrapper::queryTopics( const QString &datatype ) const
{
return RosQml::getInstance().queryTopics( datatype );
}

QList<TopicInfo> RosQmlSingletonWrapper::queryTopicInfo() const { return RosQml::getInstance().queryTopicInfo(); }

QString RosQmlSingletonWrapper::queryTopicType( const QString &name ) const
{
return RosQml::getInstance().queryTopicType( name );
}

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

QJSValue RosQmlSingletonWrapper::debug()
Expand Down
56 changes: 56 additions & 0 deletions test/communication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,62 @@ TEST( Communication, subscriber )
FAIL() << "Shouldn't have received the message that was published while the subscriber wasn't running.";
}

TEST( Communication, queryTopics )
{
ros::NodeHandle nh;
ros::Publisher pub1 = nh.advertise<geometry_msgs::Pose>( "/query_topics/pose1", 10 );
ros::Publisher pub2 = nh.advertise<geometry_msgs::Vector3>( "/query_topics/vector3", 10 );
ros::Publisher pub3 = nh.advertise<geometry_msgs::Point>( "/query_topics/point1", 10 );
ros::Publisher pub4 = nh.advertise<geometry_msgs::Point>( "/query_topics/point2", 10 );
ros::Publisher pub5 = nh.advertise<geometry_msgs::Pose>( "/query_topics/pose2", 10 );
ros::Publisher pub6 = nh.advertise<geometry_msgs::Pose>( "/query_topics/pose3", 10 );
RosQmlSingletonWrapper wrapper;
waitFor( []() { return false; } ); // Wait some time
QStringList topics = wrapper.queryTopics();
std::string debug_topics;
for ( const QString &topic : topics ) debug_topics += '\n' + topic.toStdString();
for ( const QString &topic : QStringList{ "/query_topics/pose1", "/query_topics/vector3", "/query_topics/point1",
"/query_topics/point2", "/query_topics/pose2", "/query_topics/pose3" } )
{
EXPECT_TRUE( topics.contains( topic ))
<< topic.toStdString() << " is not in topics." << std::endl << "Declared topics:" << debug_topics;
}
topics = wrapper.queryTopics( "geometry_msgs/Point" );
for ( const QString &topic : QStringList{ "/query_topics/point1", "/query_topics/point2" } )
{
EXPECT_TRUE( topics.contains( topic )) << topic.toStdString() << " is not in topics of type Point";
}
topics = wrapper.queryTopics( "geometry_msgs/Pose" );
for ( const QString &topic : QStringList{ "/query_topics/pose1", "/query_topics/pose2", "/query_topics/pose3" } )
{
EXPECT_TRUE( topics.contains( topic )) << topic.toStdString() << " is not in topics of type Pose";
}
QList<TopicInfo> topic_info = wrapper.queryTopicInfo();
for ( const QPair<QString, QString> &pair : QList<QPair<QString, QString>>{{ "/query_topics/pose1", "geometry_msgs/Pose" },
{ "/query_topics/vector3", "geometry_msgs/Vector3" },
{ "/query_topics/point1", "geometry_msgs/Point" },
{ "/query_topics/point2", "geometry_msgs/Point" },
{ "/query_topics/pose2", "geometry_msgs/Pose" },
{ "/query_topics/pose3", "geometry_msgs/Pose" }} )
{
bool found = false;
debug_topics = "";
for ( const TopicInfo &info : topic_info )
{
debug_topics += '\n' + info.name().toStdString();
if ( info.name() != pair.first ) continue;
EXPECT_EQ( info.datatype(), pair.second );
found = true;
}
EXPECT_TRUE( found ) << "Did not find " << pair.first.toStdString() << " in topic types." << std::endl << "Topics:" << debug_topics;
}

EXPECT_EQ( wrapper.queryTopicType( "/query_topics/point1" ), "geometry_msgs/Point" );
EXPECT_EQ( wrapper.queryTopicType( "/query_topics/pose2" ), "geometry_msgs/Pose" );
EXPECT_TRUE( wrapper.queryTopicType( "/query_topics/pose20" ).isEmpty())
<< wrapper.queryTopicType( "/query_topics/pose20" ).toStdString();
}

TEST( Communication, serviceCall )
{
qml_ros_plugin::Service service;
Expand Down

0 comments on commit 3eb86e0

Please sign in to comment.