Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I LOVE CIRCLES (heartbeat widget) #108

Merged
merged 12 commits into from
Feb 10, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/surface/gui/gui/pilot_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def __init__(self) -> None:
layout = QHBoxLayout()
self.setLayout(layout)

# Look into QStackedLayout for possibly switching between
# TODO 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",
Expand Down
3 changes: 2 additions & 1 deletion src/surface/gui/gui/widgets/arm.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from mavros_msgs.srv import CommandBool
from PyQt6.QtCore import pyqtSignal, pyqtSlot
from PyQt6.QtWidgets import QHBoxLayout, QPushButton, QWidget

from rov_msgs.msg import VehicleState


Expand All @@ -16,7 +17,7 @@ class Arm(QWidget):
BUTTON_HEIGHT = 60
BUTTON_STYLESHEET = 'QPushButton { font-size: 20px; }'

command_response_signal: pyqtSignal = pyqtSignal(CommandBool.Response)
command_response_signal = pyqtSignal(CommandBool.Response)
vehicle_state_signal = pyqtSignal(VehicleState)

def __init__(self) -> None:
Expand Down
67 changes: 67 additions & 0 deletions src/surface/gui/gui/widgets/circle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from typing import Callable, Optional, TypeAlias

from gui.styles.custom_styles import WidgetState
from PyQt6.QtCore import QSize, Qt
from PyQt6.QtGui import QColor
from PyQt6.QtWidgets import QPushButton, QWidget

QColorConstantsAlias: TypeAlias = QColor | Qt.GlobalColor | int
InvincibleRMC marked this conversation as resolved.
Show resolved Hide resolved


class CircleButton(QPushButton):
InvincibleRMC marked this conversation as resolved.
Show resolved Hide resolved
def __init__(self, func: Callable[[], None],
parent: Optional[QWidget] = None,
button_label: Optional[str] = None,
radius: int = 50,
color: Optional[QColorConstantsAlias] = None) -> None:
super().__init__(parent)

size = QSize(radius * 2, radius * 2)

if button_label:
self.setText(button_label)
self.setFixedSize(size)
self.clicked.connect(func)

if color is None:
style = f"QWidget {{border-radius : {radius}px;}}"
self.setStyleSheet(style)
return

if isinstance(color, Qt.GlobalColor):
color = QColor(color)
elif isinstance(color, int):
color = QColor(Qt.GlobalColor(color))

style = ("QWidget {"
f"border-radius : {radius}px;"
f"background-color: rgb({color.red()}, {color.green()}, {color.blue()})}}")
self.setStyleSheet(style)


class Circle(CircleButton):
def __init__(self, parent: Optional[QWidget] = None,
button_label: Optional[str] = None,
radius: int = 50,
color: Optional[QColorConstantsAlias] = None) -> None:
super().__init__(lambda: None, parent, button_label, radius, color)


class Indicator(Circle):
def __init__(self, parent: Optional[QWidget] = None,
button_label: Optional[str] = None,
radius: int = 50) -> None:
super().__init__(parent, button_label, radius)
self.setProperty(WidgetState.PROPERTY_NAME, WidgetState.INACTIVE)

def good_state(self) -> None:
self.setProperty(WidgetState.PROPERTY_NAME, WidgetState.ON)
style = self.style()
if style is not None:
style.polish(self)

def bad_state(self) -> None:
self.setProperty(WidgetState.PROPERTY_NAME, WidgetState.OFF)
style = self.style()
if style is not None:
style.polish(self)
6 changes: 5 additions & 1 deletion src/surface/gui/gui/widgets/debug_tab.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from gui.widgets.arm import Arm
from gui.widgets.heartbeat import HeartbeatWidget
from gui.widgets.ip_widget import IPWidget
from gui.widgets.logger import Logger
from gui.widgets.thruster_tester import ThrusterTester
from PyQt6.QtWidgets import QHBoxLayout, QVBoxLayout, QWidget
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QHBoxLayout, QVBoxLayout, QWidget


class DebugWidget(QWidget):
Expand All @@ -14,6 +15,9 @@ def __init__(self) -> None:
top_bar.addWidget(IPWidget(), alignment=Qt.AlignmentFlag.AlignTop |
Qt.AlignmentFlag.AlignLeft)

top_bar.addWidget(HeartbeatWidget(), alignment=Qt.AlignmentFlag.AlignTop |
Qt.AlignmentFlag.AlignLeft)

right_bar = QVBoxLayout()
right_bar.addWidget(ThrusterTester())
right_bar.addWidget(Arm())
Expand Down
25 changes: 17 additions & 8 deletions src/surface/gui/gui/widgets/flood_warning.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from gui.event_nodes.subscriber import GUIEventSubscriber
from gui.widgets.circle import Indicator
from PyQt6.QtCore import pyqtSignal, pyqtSlot
from PyQt6.QtGui import QFont
from PyQt6.QtWidgets import QLabel, QVBoxLayout, QWidget
from PyQt6.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QWidget

from rov_msgs.msg import Flooding


class FloodWarning(QWidget):

signal: pyqtSignal = pyqtSignal(Flooding)
signal = pyqtSignal(Flooding)

def __init__(self) -> None:
super().__init__()
Expand All @@ -18,26 +19,34 @@ def __init__(self) -> None:
# Create a latch variable
self.warning_msg_latch: bool = False
# Create basic 2 vertical stacked boxes layout
self.flood_layout = QVBoxLayout()
flood_layout = QVBoxLayout()
# Create the label that tells us what this is
self.label = QLabel('Flooding Indicator')

header_layout = QHBoxLayout()
label = QLabel('Flooding Status')
font = QFont("Arial", 14)
self.label.setFont(font)
self.flood_layout.addWidget(self.label)
label.setFont(font)
header_layout.addWidget(label)
self.indicator_circle = Indicator(radius=10)
header_layout.addWidget(self.indicator_circle)

flood_layout.addLayout(header_layout)

self.indicator = QLabel('No Water present')
self.indicator.setFont(font)
self.flood_layout.addWidget(self.indicator)
self.setLayout(self.flood_layout)
flood_layout.addWidget(self.indicator)
self.setLayout(flood_layout)

@pyqtSlot(Flooding)
def refresh(self, msg: Flooding) -> None:
if msg.flooding:
self.indicator.setText('FLOODING')
self.subscription.get_logger().error("Robot is actively flooding, do something!")
self.warning_msg_latch = True
self.indicator_circle.bad_state()
else:
self.indicator.setText('No Water present')
self.indicator_circle.good_state()
if self.warning_msg_latch:
self.subscription.get_logger().warning("Robot flooding has reset itself.")
self.warning_msg_latch = False
58 changes: 58 additions & 0 deletions src/surface/gui/gui/widgets/heartbeat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from gui.event_nodes.subscriber import GUIEventSubscriber
from gui.widgets.circle import Indicator
from PyQt6.QtCore import pyqtSignal, pyqtSlot
from PyQt6.QtGui import QFont
from PyQt6.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QWidget

from rov_msgs.msg import VehicleState


class HeartbeatWidget(QWidget):

signal = pyqtSignal(VehicleState)

def __init__(self) -> None:
super().__init__()

self.signal.connect(self.refresh)
self.subscription = GUIEventSubscriber(VehicleState, 'vehicle_state_event', self.signal)
# Create a latch variable
self.warning_msg_latch: bool = False

heartbeat_layout = QVBoxLayout()

font = QFont("Arial", 14)

pi_status_layout = QHBoxLayout()
self.pi_indicator = QLabel('No Pi Status')
self.pi_indicator.setFont(font)
pi_status_layout.addWidget(self.pi_indicator)
self.pi_indicator_circle = Indicator(radius=10)
pi_status_layout.addWidget(self.pi_indicator_circle)
heartbeat_layout.addLayout(pi_status_layout)

pi_status_layout = QHBoxLayout()
InvincibleRMC marked this conversation as resolved.
Show resolved Hide resolved
self.pixhawk_indicator = QLabel('No Pixhawk Status')
self.pixhawk_indicator.setFont(font)
pi_status_layout.addWidget(self.pixhawk_indicator)
self.pixhawk_indicator_circle = Indicator(radius=10)
pi_status_layout.addWidget(self.pixhawk_indicator_circle)
heartbeat_layout.addLayout(pi_status_layout)

self.setLayout(heartbeat_layout)

@pyqtSlot(VehicleState)
def refresh(self, msg: VehicleState) -> None:
if msg.pi_connected:
self.pi_indicator.setText('Pi Connected')
self.pi_indicator_circle.good_state()
else:
self.pi_indicator.setText('Pi Disconnected')
self.pi_indicator_circle.bad_state()

if msg.pixhawk_connected:
self.pixhawk_indicator.setText('Pixhawk Connected')
self.pixhawk_indicator_circle.good_state()
else:
self.pixhawk_indicator.setText('Pixhawk Disconnected')
self.pixhawk_indicator_circle.bad_state()