Skip to content

Commit

Permalink
Implement menu to support custom topic names (#93)
Browse files Browse the repository at this point in the history
* Implement menu to support custom topic names

* Topic name dialog fixes

* Please Python Linter

Signed-off-by: Juan Lopez Fernandez <juanlopez@eprosima.com>

* Add mention to user documentation

Signed-off-by: Juan Lopez Fernandez <juanlopez@eprosima.com>

---------

Signed-off-by: Juan Lopez Fernandez <juanlopez@eprosima.com>
Co-authored-by: Kyle Semelka <kyle.semelka@figure.ai>
  • Loading branch information
juanlofer-eprosima and kyle-figure authored Nov 17, 2023
1 parent 445b11a commit 7b678b9
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 10 deletions.
6 changes: 3 additions & 3 deletions controller/controller_tool/tool/src/py/Controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def is_valid_dds_domain(self, dds_domain):
"""Check if DDS Domain is valid."""
return ((dds_domain >= 0) and (dds_domain <= MAX_DDS_DOMAIN_ID))

def init_dds(self, dds_domain):
def init_dds(self, dds_domain, command_topic, status_topic):
# Check DDS Domain
if (not self.is_valid_dds_domain(dds_domain)):
raise ValueError(
Expand All @@ -151,7 +151,7 @@ def init_dds(self, dds_domain):
command_topic_qos = fastdds.TopicQos()
self.participant.get_default_topic_qos(command_topic_qos)
self.command_topic = self.participant.create_topic(
'/ddsrecorder/command',
command_topic,
self.command_topic_data_type.getName(),
command_topic_qos)

Expand All @@ -164,7 +164,7 @@ def init_dds(self, dds_domain):
status_topic_qos = fastdds.TopicQos()
self.participant.get_default_topic_qos(status_topic_qos)
self.status_topic = self.participant.create_topic(
'/ddsrecorder/status',
status_topic,
self.status_topic_data_type.getName(),
status_topic_qos)

Expand Down
88 changes: 81 additions & 7 deletions controller/controller_tool/tool/src/py/ControllerGUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from PyQt6.QtGui import QAction, QDesktopServices
from PyQt6.QtWidgets import (
QDialog, QDialogButtonBox, QHBoxLayout, QHeaderView,
QLabel, QMainWindow, QMenuBar, QPushButton, QSpinBox, QTableWidget,
QLabel, QLineEdit, QMainWindow, QMenuBar, QPushButton, QSpinBox, QTableWidget,
QTableWidgetItem, QVBoxLayout, QWidget)

DDS_RECORDER = 'DDS Recorder'
Expand Down Expand Up @@ -83,6 +83,56 @@ def get_dds_domain(self):
return int(self.spin_box.value())


class DdsTopicNameDialog(QDialog):
"""Class that implements the a dialog to set the DDS command and status topic names."""

def __init__(self, current_command_topic, current_status_topic):
"""Construct the dialog to set the DDS command and status topic names."""
super().__init__()

self.command_topic_label = QLabel('Command topic:')
self.command_topic_label.setFixedWidth(120)
self.command_text_box = QLineEdit(self)
self.command_text_box.setText(current_command_topic)
self.command_text_box.setMinimumWidth(250)

self.status_topic_label = QLabel('Status topic:')
self.status_topic_label.setFixedWidth(120)
self.status_text_box = QLineEdit(self)
self.status_text_box.setText(current_status_topic)
self.status_text_box.setMinimumWidth(250)

self.buttonBox = QDialogButtonBox(
QDialogButtonBox.StandardButton.Save
| QDialogButtonBox.StandardButton.Cancel)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)

self.command_layout = QHBoxLayout()
self.command_layout.addWidget(self.command_topic_label)
self.command_layout.addWidget(self.command_text_box)

self.status_layout = QHBoxLayout()
self.status_layout.addWidget(self.status_topic_label)
self.status_layout.addWidget(self.status_text_box)

layout = QVBoxLayout()
layout.addLayout(self.command_layout)
layout.addLayout(self.status_layout)
layout.addWidget(self.buttonBox)
layout.setSizeConstraint(QVBoxLayout.SizeConstraint.SetFixedSize)

self.setLayout(layout)

def get_command_topic(self):
"""Return DDS command topic from the text box."""
return self.command_text_box.text()

def get_status_topic(self):
"""Return DDS status topic from the text box."""
return self.status_text_box.text()


class MenuWidget(QMenuBar):
"""Class that implements the menu of DDS Recorder controller GUI."""

Expand All @@ -99,6 +149,10 @@ def __init__(self, main_window):
dds_domain_action.triggered.connect(self.main_window.dds_domain_dialog)
file_menu.addAction(dds_domain_action)

topic_name_action = QAction('DDS Topics', self)
topic_name_action.triggered.connect(self.main_window.topic_name_dialog)
file_menu.addAction(topic_name_action)

docs_action = QAction('Documentation', self)
docs_action.triggered.connect(
lambda: QDesktopServices.openUrl(
Expand Down Expand Up @@ -145,6 +199,8 @@ def __init__(self):

self.dds_controller = Controller()
self.dds_domain = 0
self.command_topic = '/ddsrecorder/command'
self.status_topic = '/ddsrecorder/status'

self.init_gui()

Expand All @@ -156,7 +212,7 @@ def __init__(self):

# Create DDS entities at the end to avoid race condition (receive
# status before connecting with signal slots)
self.dds_controller.init_dds(self.dds_domain)
self.dds_controller.init_dds(self.dds_domain, self.command_topic, self.status_topic)

def on_ddsrecorder_discovered(self, discovered, message):
"""Inform that a new DDS Recorder has been discovered."""
Expand All @@ -178,17 +234,25 @@ def on_ddsrecorder_status(self, previous_status, current_status, info):
# Change status bar
self.update_status(RecorderStatus[current_status.upper()])

def restart_controller(self, dds_domain=0):
"""Restart the DDS Controller if the DDS Domain changes."""
if dds_domain != self.dds_domain:
def restart_controller(
self,
dds_domain=0,
command_topic='/ddsrecorder/command',
status_topic='/ddsrecorder/status'):
"""Restart the DDS Controller if the DDS Domain or topic name changes."""
if (dds_domain != self.dds_domain
or command_topic != self.command_topic
or status_topic != self.status_topic):
if self.dds_controller.is_valid_dds_domain(dds_domain):
# Delete DDS entities in previous domain
self.dds_controller.delete_dds()
# Reset status
self.update_status(RecorderStatus.CLOSED)
# Create DDS entities in new domain
self.dds_controller.init_dds(dds_domain)
self.dds_controller.init_dds(dds_domain, command_topic, status_topic)
self.dds_domain = dds_domain
self.command_topic = command_topic
self.status_topic = status_topic

def init_gui(self):
"""Initialize the graphical interface and its widgets."""
Expand Down Expand Up @@ -317,7 +381,17 @@ def dds_domain_dialog(self):
# Restart only if it is a different Domain
if domain != self.dds_domain:
if (self.dds_controller.is_valid_dds_domain(domain)):
self.restart_controller(dds_domain=domain)
self.restart_controller(domain, self.command_topic, self.status_topic)

def topic_name_dialog(self):
"""Create a dialog to update the recorder topic names."""
dialog = DdsTopicNameDialog(self.command_topic, self.status_topic)
if dialog.exec():
command_topic = dialog.get_command_topic()
status_topic = dialog.get_status_topic()
# Restart controller if a topic name has changed
if command_topic != self.command_topic or status_topic != self.status_topic:
self.restart_controller(self.dds_domain, command_topic, status_topic)

def event_start_button_clicked(self):
"""Publish command."""
Expand Down
2 changes: 2 additions & 0 deletions docs/rst/recording/remote_control/remote_control.rst
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ If the controller should function in a domain different than the default one (``
.. figure:: /rst/figures/controller_domain.png
:align: center

It is also possible to use non-default status and command topic names through the ``File->DDS Topics`` dialog.

When a |ddsrecorder| instance is found in the domain, a message is displayed in the logging panel:

.. figure:: /rst/figures/controller_found.png
Expand Down

0 comments on commit 7b678b9

Please sign in to comment.