Skip to content

Commit

Permalink
Added graph queries getTopicNamesAndTypes, getServiceNamesAndTypes an…
Browse files Browse the repository at this point in the history
…d getActionNamesAndTypes to Ros2 singleton.
  • Loading branch information
StefanFabian committed Oct 22, 2024
1 parent b740c78 commit b30874c
Show file tree
Hide file tree
Showing 4 changed files with 236 additions and 30 deletions.
33 changes: 23 additions & 10 deletions docs/pages/Ros2-Singleton.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,31 @@ As described in the API documentation for :cpp:func:`Ros2.init <qml_ros2_plugin:
node name or additionally use provided command line args instead of the command
line args provided to your executable.

Query Topics
Query Graph
------------

You can also use the Ros2 singleton to query the available topics.
Currently, three methods are provided:
You can also use the Ros2 singleton to query the available topics, services and actions.
These are available as:

* | ``QVariantMap getTopicNamesAndTypes()``
| Retrieves a map of topic names and their types as a list of strings.
* | ``QVariantMap getServiceNamesAndTypes()``
| Retrieves a map of service names and their types as a list of strings.
* | ``QVariantMap getActionNamesAndTypes()``
| Retrieves a map of action names and their types as a list of strings.
Note that the return value is a ``QVariantMap``, which is a wrapped ``QMap<QString, QStringList>``
to make it compatible with QML.
Also check out the ``graph_queries.qml`` example in the repo's examples folder.

Additionally, for topics three convenience methods are also 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 )``
| Retrieves the datatype for a given topic.
| Retrieves a list of all advertised topics including their datatypes. See :cpp:class:`TopicInfo`
* | ``QString queryTopicTypes( const QString &name )``
| Retrieves the datatypes for a given topic.
Example:

Expand All @@ -50,15 +63,15 @@ Example:
var cameraTopics = []
var topics = Ros2.queryTopicInfo()
for (var i = 0; i < topics.length; ++i) {
if (topics[i].datatype == "sensor_msgs/msg/Image") cameraTopics.push(topics[i].name)
if (topics[i].datatypes.includes("sensor_msgs/msg/Image")) cameraTopics.push(topics[i].name)
}
// The type of a specific topic can be retrieved as follows
var datatype = Ros2.queryTopicType("/topic/that/i/care/about")
// The types of a specific topic can be retrieved as follows
var datatypes = Ros2.queryTopicTypes("/topic/that/i/care/about")
// Using this we can make an even worse implementation of the same functionality
var cameraTopics = []
var topics = Ros2.queryTopics() // Gets all topics
for (var i = 0; i < topics.length; ++i) {
if (Ros2.queryTopicType(topics[i]) == "sensor_msgs/msg/Image") cameraTopics.push(topics[i])
if (Ros2.queryTopicTypes(topics[i]).includes("sensor_msgs/msg/Image")) cameraTopics.push(topics[i])
}
Create Empty Message
Expand Down
82 changes: 82 additions & 0 deletions examples/graph_queries.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import QtQuick 2.6
import QtQuick.Controls 2.2
import QtQuick.Controls.Material 2.2
import QtQuick.Layouts 1.0
import Ros2 1.0

ApplicationWindow {
id: page
width: 620
height: 400

// This connection makes sure the application exits if this ROS node is requested to shutdown
Connections {
target: Ros2
function onShutdown() {
Qt.quit()
}
}
property var topics: ({})
property var services: ({})
property var actions: ({})

function update() {
Ros2.info("Updating.")
topics = Ros2.getTopicNamesAndTypes()
services = Ros2.getServiceNamesAndTypes()
actions = Ros2.getActionNamesAndTypes()
}
function printNamesAndTypes(map) {
let result = ""
for (let key in map) {
result += key + ": " + map[key] + "\n"
}
return result
}

ScrollView {
anchors.fill: parent

ColumnLayout {
anchors.margins: 12


Button {
Layout.columnSpan: 3
text: "Update"
onClicked: page.update()
}

Text {
text: "Topics:"
}
Text {
leftPadding: 12
text: printNamesAndTypes(topics)
}
Text {
text: "Services:"
}
Text {
leftPadding: 12
text: printNamesAndTypes(services)
}
Text {
text: "Actions:"
}
Text {
leftPadding: 12
text: printNamesAndTypes(actions)
}
}
}

Component.onCompleted: {
// Initialize ROS with the given name. The command line args are passed by the plugin
// Optionally, you can call init with a string list ["arg1", "arg2"] after the name to use those
// args instead of the ones supplied by the command line.
Ros2.init("qml_graph_queries_demo")
update()
}

}
31 changes: 29 additions & 2 deletions include/qml_ros2_plugin/ros2.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,26 @@ class Ros2Qml : public QObject
*/
QStringList queryTopicTypes( const QString &name ) const;

/*!
* Queries the internal node for its known topics and their types.
* See rclcpp::Node::get_topic_names_and_types() for more information.
* @return A map with the topic names as keys and the types as values.
*/
QMap<QString, QStringList> getTopicNamesAndTypes() const;

/*!
* Queries the internal node for its known services and their types.
* See rclcpp::Node::get_service_names_and_types() for more information.
* @return A map with the service names as keys and the types as values.
*/
QMap<QString, QStringList> getServiceNamesAndTypes() const;

/*!
* Queries the internal node for its known actions and their types.
* @return A map with the action names as keys and the types as values.
*/
QMap<QString, QStringList> getActionNamesAndTypes() const;

/*!
* Creates an empty message for the given message type, e.g., "geometry_msgs/Point".
* If the message type is known, an empty message with all members set to their default is returned.
Expand Down Expand Up @@ -115,9 +135,7 @@ class Ros2Qml : public QObject
//! Emitted when this ROS node was shut down and it is time to exit.
void shutdown();


private:

std::thread executor_thread_;
std::shared_ptr<rclcpp::Context> context_;
std::shared_ptr<rclcpp::Node> node_;
Expand Down Expand Up @@ -169,6 +187,15 @@ class Ros2QmlSingletonWrapper : public QObject
//! @copydoc Ros2Qml::queryTopicTypes
Q_INVOKABLE QStringList queryTopicTypes( const QString &name ) const;

//! @copydoc Ros2Qml::getTopicNamesAndTypes
Q_INVOKABLE QVariantMap getTopicNamesAndTypes() const;

//! @copydoc Ros2Qml::getServiceNamesAndTypes
Q_INVOKABLE QVariantMap getServiceNamesAndTypes() const;

//! @copydoc Ros2Qml::getActionNamesAndTypes
Q_INVOKABLE QVariantMap getActionNamesAndTypes() const;

//! @copydoc Ros2Qml::createEmptyMessage
Q_INVOKABLE QVariant createEmptyMessage( const QString &datatype ) const;

Expand Down
120 changes: 102 additions & 18 deletions src/ros2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include <QCoreApplication>
#include <QJSEngine>
#include <rcl_action/graph.h>
#include <thread>

namespace qml_ros2_plugin
Expand Down Expand Up @@ -89,8 +90,7 @@ QStringList Ros2Qml::queryTopics( const QString &datatype ) const
QML_ROS2_PLUGIN_ERROR( "Tried to query topics before node was initialized!" );
return {};
}
std::map<std::string, std::vector<std::string>> topics_and_types =
node_->get_topic_names_and_types();
auto topics_and_types = node_->get_topic_names_and_types();
QStringList result;
std::string std_datatype = toFullyQualifiedDatatype( datatype );
for ( const auto &[topic, types] : topics_and_types ) {
Expand All @@ -104,15 +104,14 @@ QStringList Ros2Qml::queryTopics( const QString &datatype ) const

QList<TopicInfo> Ros2Qml::queryTopicInfo() const
{
std::map<std::string, std::vector<std::string>> topics_and_types =
node_->get_topic_names_and_types();
auto topics_and_types = node_->get_topic_names_and_types();
QList<TopicInfo> result;
for ( const auto &topic : topics_and_types ) {
QStringList types;
types.reserve( static_cast<int>( topic.second.size() ) );
std::transform( topic.second.begin(), topic.second.end(), std::back_inserter( types ),
for ( const auto &[topic, types] : topics_and_types ) {
QStringList result_types;
result_types.reserve( static_cast<int>( types.size() ) );
std::transform( types.begin(), types.end(), std::back_inserter( result_types ),
QString::fromStdString );
result.append( { QString::fromStdString( topic.first ), types } );
result.append( { QString::fromStdString( topic ), result_types } );
}
return result;
}
Expand All @@ -121,21 +120,76 @@ QStringList Ros2Qml::queryTopicTypes( const QString &name ) const
{
if ( name.isEmpty() )
return {};
std::map<std::string, std::vector<std::string>> topics_and_types =
node_->get_topic_names_and_types();
auto topics_and_types = node_->get_topic_names_and_types();
std::string std_name = name.toStdString();
for ( const auto &topic : topics_and_types ) {
if ( std_name != topic.first )
for ( const auto &[topic, types] : topics_and_types ) {
if ( std_name != topic )
continue;
QStringList types;
types.reserve( static_cast<int>( topic.second.size() ) );
std::transform( topic.second.begin(), topic.second.end(), std::back_inserter( types ),
QString::fromStdString );
return types;
QStringList result;
result.reserve( static_cast<int>( types.size() ) );
std::transform( types.begin(), types.end(), std::back_inserter( result ), QString::fromStdString );
return result;
}
return {};
}

QMap<QString, QStringList> Ros2Qml::getTopicNamesAndTypes() const
{
auto topics_and_types = node_->get_topic_names_and_types();
QMap<QString, QStringList> result;
for ( const auto &[topic, types] : topics_and_types ) {
QStringList result_types;
result_types.reserve( static_cast<int>( types.size() ) );
std::transform( types.begin(), types.end(), std::back_inserter( result_types ),
QString::fromStdString );
result.insert( QString::fromStdString( topic ), result_types );
}
return result;
}

QMap<QString, QStringList> Ros2Qml::getServiceNamesAndTypes() const
{
auto service_names_and_types = node_->get_service_names_and_types();
QMap<QString, QStringList> result;
for ( const auto &[service_name, types] : service_names_and_types ) {
QStringList result_types;
result_types.reserve( static_cast<int>( types.size() ) );
std::transform( types.begin(), types.end(), std::back_inserter( result_types ),
QString::fromStdString );
result.insert( QString::fromStdString( service_name ), result_types );
}
return result;
}

QMap<QString, QStringList> Ros2Qml::getActionNamesAndTypes() const
{
rcl_names_and_types_t names_and_types = rcl_get_zero_initialized_names_and_types();
const rcl_node_s *node_handle = node_->get_node_base_interface()->get_rcl_node_handle();
rcl_allocator_t allocator = rcutils_get_default_allocator();
if ( rcl_ret_t ret = rcl_action_get_names_and_types( node_handle, &allocator, &names_and_types );
ret != RCL_RET_OK ) {
QML_ROS2_PLUGIN_ERROR( "Failed to get action names and types: %s", rcl_get_error_string().str );
return {};
}
QMap<QString, QStringList> result;
for ( size_t i = 0; i < names_and_types.names.size; ++i ) {
const std::string name = names_and_types.names.data[i];
const auto &types = names_and_types.types[i];
QStringList result_types;
result_types.reserve( static_cast<int>( types.size ) );
for ( size_t j = 0; j < types.size; ++j ) {
result_types.append( QString::fromStdString( types.data[j] ) );
}
result.insert( QString::fromStdString( name ), result_types );
}
if ( rcl_names_and_types_fini( &names_and_types ) != RCL_RET_OK ) {
QML_ROS2_PLUGIN_ERROR(
"Failed to free action names and types: '%s'. Might have lost some memory.",
rcl_get_error_string().str );
}
return result;
}

QVariant Ros2Qml::createEmptyMessage( const QString &datatype ) const
{
try {
Expand Down Expand Up @@ -254,6 +308,36 @@ QStringList Ros2QmlSingletonWrapper::queryTopicTypes( const QString &name ) cons
return Ros2Qml::getInstance().queryTopicTypes( name );
}

QVariantMap Ros2QmlSingletonWrapper::getTopicNamesAndTypes() const
{
QVariantMap result;
QMap<QString, QStringList> topic_names_and_types = Ros2Qml::getInstance().getTopicNamesAndTypes();
for ( auto it = topic_names_and_types.begin(); it != topic_names_and_types.end(); ++it ) {
result.insert( it.key(), QVariant( it.value() ) );
}
return result;
}

QVariantMap Ros2QmlSingletonWrapper::getServiceNamesAndTypes() const
{
QVariantMap result;
QMap<QString, QStringList> topic_names_and_types = Ros2Qml::getInstance().getServiceNamesAndTypes();
for ( auto it = topic_names_and_types.begin(); it != topic_names_and_types.end(); ++it ) {
result.insert( it.key(), QVariant( it.value() ) );
}
return result;
}

QVariantMap Ros2QmlSingletonWrapper::getActionNamesAndTypes() const
{
QVariantMap result;
QMap<QString, QStringList> topic_names_and_types = Ros2Qml::getInstance().getActionNamesAndTypes();
for ( auto it = topic_names_and_types.begin(); it != topic_names_and_types.end(); ++it ) {
result.insert( it.key(), QVariant( it.value() ) );
}
return result;
}

QVariant Ros2QmlSingletonWrapper::createEmptyMessage( const QString &datatype ) const
{
return Ros2Qml::getInstance().createEmptyMessage( datatype );
Expand Down

0 comments on commit b30874c

Please sign in to comment.