diff --git a/src/pi/pi_main/launch/pi_launch.py b/src/pi/pi_main/launch/pi_launch.py index 723c6ae6..7865c060 100644 --- a/src/pi/pi_main/launch/pi_launch.py +++ b/src/pi/pi_main/launch/pi_launch.py @@ -80,7 +80,8 @@ def generate_launch_description() -> LaunchDescription: package='pi_main', executable='ip_publisher', emulate_tty=True, - output='screen' + output='screen', + remappings=[('/pi/ip_address', '/tether/ip_address')] ) namespace_launch = GroupAction( diff --git a/src/pi/pi_main/setup.py b/src/pi/pi_main/setup.py index ace24d6e..917890ff 100644 --- a/src/pi/pi_main/setup.py +++ b/src/pi/pi_main/setup.py @@ -21,7 +21,7 @@ (os.path.join('share', PACKAGE_NAME, 'udev_rules'), glob('udev_rules/*')) ], - install_requires=['setuptools', 'flake8==4.0.1', 'mypy >= 1.7', 'socket'], + install_requires=['setuptools', 'flake8==4.0.1', 'mypy >= 1.7'], zip_safe=True, maintainer='Michael Carlstrom', maintainer_email='rmc170@case.edu', diff --git a/src/rov_msgs/msg/IPAddress.msg b/src/rov_msgs/msg/IPAddress.msg index 0c1bd82b..576fb675 100644 --- a/src/rov_msgs/msg/IPAddress.msg +++ b/src/rov_msgs/msg/IPAddress.msg @@ -1,2 +1,3 @@ -string ethernet_address "N/A" -string wireless_address "N/A" \ No newline at end of file +# IPv4 Addresses never longer than 15 characters +string<=15 ethernet_address "N/A" +string<=15 wireless_address "N/A" \ No newline at end of file diff --git a/src/surface/gui/gui/operator_app.py b/src/surface/gui/gui/operator_app.py index 8b141ee9..a7bf96ff 100644 --- a/src/surface/gui/gui/operator_app.py +++ b/src/surface/gui/gui/operator_app.py @@ -1,10 +1,9 @@ from gui.app import App from gui.widgets.logger import Logger from gui.widgets.debug_tab import DebugWidget -from gui.widgets.seagrass import SeagrassWidget from gui.widgets.task_selector import TaskSelector from gui.widgets.timer import Timer -from PyQt6.QtWidgets import QGridLayout, QTabWidget, QWidget +from PyQt6.QtWidgets import QGridLayout, QTabWidget, QWidget, QVBoxLayout class OperatorApp(App): @@ -15,26 +14,25 @@ def __init__(self) -> None: # Main tab main_tab = QWidget() - main_layout: QGridLayout = QGridLayout() + main_layout = QGridLayout() main_tab.setLayout(main_layout) - self.timer: Timer = Timer() - main_layout.addWidget(self.timer, 0, 1) + timer = Timer() + main_layout.addWidget(timer, 0, 1) - self.task_selector: TaskSelector = TaskSelector() - main_layout.addWidget(self.task_selector, 1, 1) + task_selector = TaskSelector() + main_layout.addWidget(task_selector, 1, 1) - self.logger: Logger = Logger() - main_layout.addWidget(self.logger, 1, 0) + logger = Logger() + main_layout.addWidget(logger, 1, 0) # Add tabs to root - root_layout: QGridLayout = QGridLayout() + root_layout = QVBoxLayout() self.setLayout(root_layout) tabs = QTabWidget() tabs.addTab(main_tab, "Main") tabs.addTab(DebugWidget(), "Debug") - tabs.addTab(SeagrassWidget(), "Seagrass") root_layout.addWidget(tabs) diff --git a/src/surface/gui/gui/pilot_app.py b/src/surface/gui/gui/pilot_app.py index 71cffb8f..b20836cb 100644 --- a/src/surface/gui/gui/pilot_app.py +++ b/src/surface/gui/gui/pilot_app.py @@ -12,25 +12,27 @@ def __init__(self) -> None: self.setWindowTitle('Pilot GUI - CWRUbotix ROV 2024') - layout: QHBoxLayout = QHBoxLayout() + layout = QHBoxLayout() self.setLayout(layout) - self.video_area = SwitchableVideoWidget(["front_cam/image_raw", - "bottom_cam/image_raw", - "camera/color/image_raw"], - ["Front Camera", - "Bottom Camera", - "Depth Camera"], - "camera_switch") - layout.addWidget(self.video_area, alignment=Qt.AlignmentFlag.AlignCenter) - - self.floodWidget: FloodWarning = FloodWarning() - layout.addWidget(self.floodWidget, alignment=Qt.AlignmentFlag.AlignRight - | Qt.AlignmentFlag.AlignTop) - - self.arm: Arm = Arm() - layout.addWidget(self.arm, - alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignBottom) + # Look into QStackedLayout for possibly switching between + # 1 big camera feed and 2 smaller ones + video_area = SwitchableVideoWidget(["front_cam/image_raw", + "bottom_cam/image_raw", + "camera/color/image_raw"], + ["Front Camera", + "Bottom Camera", + "Depth Camera"], + "camera_switch") + layout.addWidget(video_area, alignment=Qt.AlignmentFlag.AlignCenter) + + floodWidget = FloodWarning() + layout.addWidget(floodWidget, alignment=Qt.AlignmentFlag.AlignRight | + Qt.AlignmentFlag.AlignTop) + + arm = Arm() + layout.addWidget(arm, alignment=Qt.AlignmentFlag.AlignRight | + Qt.AlignmentFlag.AlignBottom) def run_gui_pilot() -> None: diff --git a/src/surface/gui/gui/widgets/debug_tab.py b/src/surface/gui/gui/widgets/debug_tab.py index c287cf26..5f5d3a22 100644 --- a/src/surface/gui/gui/widgets/debug_tab.py +++ b/src/surface/gui/gui/widgets/debug_tab.py @@ -1,23 +1,29 @@ +from gui.widgets.arm import Arm +from gui.widgets.ip_widget import IPWidget from gui.widgets.logger import Logger from gui.widgets.thruster_tester import ThrusterTester -from gui.widgets.arm import Arm from PyQt6.QtWidgets import QHBoxLayout, QVBoxLayout, QWidget +from PyQt6.QtCore import Qt class DebugWidget(QWidget): def __init__(self) -> None: super().__init__() + top_bar = QHBoxLayout() + top_bar.addWidget(IPWidget(), alignment=Qt.AlignmentFlag.AlignTop | + Qt.AlignmentFlag.AlignLeft) + right_bar = QVBoxLayout() right_bar.addWidget(ThrusterTester()) right_bar.addWidget(Arm()) - right_bar.addStretch() + right_bar.setAlignment(Qt.AlignmentFlag.AlignRight) - top_pane = QHBoxLayout() - top_pane.addStretch(2) - top_pane.addLayout(right_bar) + top_bar.addStretch(2) + top_bar.addLayout(right_bar) - root_layout = QVBoxLayout(self) - root_layout.addLayout(top_pane) + root_layout = QVBoxLayout() + root_layout.addLayout(top_bar) root_layout.addStretch() root_layout.addWidget(Logger()) + self.setLayout(root_layout) diff --git a/src/surface/gui/gui/widgets/flood_warning.py b/src/surface/gui/gui/widgets/flood_warning.py index 2db78541..29a9aed7 100644 --- a/src/surface/gui/gui/widgets/flood_warning.py +++ b/src/surface/gui/gui/widgets/flood_warning.py @@ -12,25 +12,23 @@ class FloodWarning(QWidget): def __init__(self) -> None: super().__init__() - # Boilerplate PyQt Setup to link to ROS through a signal/subscriber + self.signal.connect(self.refresh) - self.subscription: GUIEventSubscriber = GUIEventSubscriber(Flooding, - 'flooding', - self.signal) + self.subscription = GUIEventSubscriber(Flooding, 'flooding', self.signal) # Create a latch variable self.warning_msg_latch: bool = False # Create basic 2 vertical stacked boxes layout - self.flood_layour = QVBoxLayout() + self.flood_layout = QVBoxLayout() # Create the label that tells us what this is - self.label: QLabel = QLabel('Flooding Indicator') - font: QFont = QFont("Arial", 14) + self.label = QLabel('Flooding Indicator') + font = QFont("Arial", 14) self.label.setFont(font) - self.flood_layour.addWidget(self.label) + self.flood_layout.addWidget(self.label) - self.indicator: QLabel = QLabel('No Water present') + self.indicator = QLabel('No Water present') self.indicator.setFont(font) - self.flood_layour.addWidget(self.indicator) - self.setLayout(self.flood_layour) + self.flood_layout.addWidget(self.indicator) + self.setLayout(self.flood_layout) @pyqtSlot(Flooding) def refresh(self, msg: Flooding) -> None: diff --git a/src/surface/gui/gui/widgets/ip_widget.py b/src/surface/gui/gui/widgets/ip_widget.py new file mode 100644 index 00000000..e37ceefc --- /dev/null +++ b/src/surface/gui/gui/widgets/ip_widget.py @@ -0,0 +1,31 @@ +from gui.event_nodes.subscriber import GUIEventSubscriber +from PyQt6.QtCore import pyqtSignal, pyqtSlot +from PyQt6.QtWidgets import QLabel, QVBoxLayout, QWidget + +from rov_msgs.msg import IPAddress + + +class IPWidget(QWidget): + + signal = pyqtSignal(IPAddress) + + def __init__(self) -> None: + super().__init__() + + self.signal.connect(self.refresh) + self.sub = GUIEventSubscriber(IPAddress, 'ip_address', self.signal) + + ip_layout = QVBoxLayout() + wired_str = f'Last known Pi Wired IP: {IPAddress.ETHERNET_ADDRESS__DEFAULT}' + wireless_str = f'Last known Pi Wireless IP: {IPAddress.WIRELESS_ADDRESS__DEFAULT}' + self.ethernet_label = QLabel(wired_str) + self.wireless_label = QLabel(wireless_str) + + ip_layout.addWidget(self.ethernet_label) + ip_layout.addWidget(self.wireless_label) + self.setLayout(ip_layout) + + @pyqtSlot(IPAddress) + def refresh(self, msg: IPAddress) -> None: + self.ethernet_label.setText(f'Last known Pi Wired IP: {msg.ethernet_address}') + self.wireless_label.setText(f'Last known Pi Wireless IP: {msg.wireless_address}') diff --git a/src/surface/gui/gui/widgets/thruster_tester.py b/src/surface/gui/gui/widgets/thruster_tester.py index d16cb84c..3b2b51ea 100644 --- a/src/surface/gui/gui/widgets/thruster_tester.py +++ b/src/surface/gui/gui/widgets/thruster_tester.py @@ -12,7 +12,7 @@ class ThrusterTester(QWidget): """Widget to command the pixhawk to test the thrusters, and reassign thruster ports.""" - TEST_LENGTH: float = 2.0 # time between adjecent tests of individual thrusters + TEST_LENGTH: float = 2.0 # time between adjacent tests of individual thrusters TEST_THROTTLE: float = 0.50 # 50% MOTOR_COUNT = 8 @@ -63,7 +63,7 @@ def __init__(self) -> None: test_button = QPushButton() test_button.setText("Test Thrusters") - test_button.clicked.connect(self.asnyc_send_message) + test_button.clicked.connect(self.async_send_message) layout.addWidget(heading) layout.addLayout(pin_numbers_grid) @@ -105,7 +105,7 @@ def test_motor_for_time(self, motor_index: int, throttle: float, duration: float time.sleep(0.05) - def asnyc_send_message(self) -> None: + def async_send_message(self) -> None: Thread(target=self.send_test_message, daemon=True, name="thruster_test_thread").start() def send_test_message(self) -> None: diff --git a/src/surface/gui/launch/operator_launch.py b/src/surface/gui/launch/operator_launch.py index 860a8427..8a415cdf 100644 --- a/src/surface/gui/launch/operator_launch.py +++ b/src/surface/gui/launch/operator_launch.py @@ -1,7 +1,7 @@ +from launch.actions import GroupAction from launch.launch_description import LaunchDescription from launch.substitutions import LaunchConfiguration from launch_ros.actions import Node, PushRosNamespace -from launch.actions import GroupAction def generate_launch_description() -> LaunchDescription: @@ -18,7 +18,8 @@ def generate_launch_description() -> LaunchDescription: ("/surface/gui/task_feedback", "/surface/task_feedback"), ("/surface/gui/auto_docker_control", "/surface/auto_docker_control"), ("/surface/gui/vehicle_state_event", "/surface/vehicle_state_event"), - ("/surface/gui/mavros/cmd/arming", "/tether/mavros/cmd/arming")], + ("/surface/gui/mavros/cmd/arming", "/tether/mavros/cmd/arming"), + ("/surface/gui/ip_address", "/tether/ip_address")], emulate_tty=True, output='screen' )