From e487b2c75fc95cee0178e22dd4aa810b364a8c8a Mon Sep 17 00:00:00 2001 From: Muk Date: Wed, 23 Oct 2024 21:18:13 -0700 Subject: [PATCH 01/17] set up UI --- .../drive_and_dribbler_widget.py | 275 ++++++++++++++---- 1 file changed, 220 insertions(+), 55 deletions(-) diff --git a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py index 68bdbd2738..c8360f61ab 100644 --- a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py +++ b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py @@ -16,17 +16,26 @@ def __init__(self, proto_unix_io: ProtoUnixIO) -> None: self.input_a = time.time() self.constants = tbots_cpp.create2021RobotConstants() QWidget.__init__(self) + layout = QVBoxLayout() + self.drive_widget = QStackedWidget() self.proto_unix_io = proto_unix_io - # Add widgets to layout - layout.addWidget(self.setup_direct_velocity("Drive")) + # create swappable widget system using stacked widgets + self.direct_velocity_widget = self.setup_direct_velocity("Drive - Direct Velocity") + self.per_motor_widget = self.setup_per_motor("Drive - Per Motor") + self.drive_widget.addWidget(self.direct_velocity_widget) + self.drive_widget.addWidget(self.per_motor_widget) + + layout.addWidget(self.setup_drive_switch("Drive Mode Switch")) + layout.addWidget(self.drive_widget) layout.addWidget(self.setup_dribbler("Dribbler")) self.enabled = True - self.setLayout(layout) + self.toggle_control_mode(True) + self.toggle_dribbler_sliders(True) def refresh(self) -> None: """Refresh the widget and send the a MotorControl message with the current values""" @@ -54,6 +63,38 @@ def value_change(self, value: float) -> str: value_str = "%.2f" % value return value_str + def setup_drive_switch(self, title: str) -> QGroupBox: + """Create a widget to switch between per-motor and velocity control modes + + :param title: group box name + """ + group_box = QGroupBox(title) + dbox = QVBoxLayout() + + # Each button disables itself after being pressed. + self.use_per_motor = QPushButton("Per-Motor") + self.use_per_motor.clicked.connect(self.switch_to_motor) + + self.use_direct_velocity = QPushButton("Velocity Control") + self.use_direct_velocity.clicked.connect(self.switch_to_velocity) + + dbox.addWidget( + self.use_direct_velocity, alignment=Qt.AlignmentFlag.AlignCenter + ) + dbox.addWidget( + self.use_per_motor, alignment=Qt.AlignmentFlag.AlignCenter + ) + + group_box.setLayout(dbox) + + return group_box + + def switch_to_velocity(self): + self.toggle_control_mode(True) + + def switch_to_motor(self): + self.toggle_control_mode(False) + def setup_direct_velocity(self, title: str) -> QGroupBox: """Create a widget to control the direct velocity of the robot's motors @@ -121,6 +162,90 @@ def setup_direct_velocity(self, title: str) -> QGroupBox: return group_box + def setup_per_motor(self, title: str) -> QGroupBox: + """Create a widget to control the rotation rate of each motor + + :param title: the name of the group box + """ + group_box = QGroupBox(title) + dbox = QVBoxLayout() + + ( + fl_layout, + self.front_left_motor_slider, + self.front_left_motor_label, + ) = common_widgets.create_float_slider( + "Front Left Motor (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + fr_layout, + self.front_right_motor_slider, + self.front_right_motor_label, + ) = common_widgets.create_float_slider( + "Front Right Motor (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + bl_layout, + self.back_left_motor_slider, + self.back_left_motor_label, + ) = common_widgets.create_float_slider( + "Back Left Motor (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + br_layout, + self.back_right_motor_slider, + self.back_right_motor_label, + ) = common_widgets.create_float_slider( + "Back Right Motor (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + + # Add listener functions for each motor's slider to update labels with slider values + common_widgets.enable_slider( + self.front_left_motor_slider, self.front_left_motor_label, self.value_change + ) + common_widgets.enable_slider( + self.front_right_motor_slider, self.front_right_motor_label, self.value_change + ) + common_widgets.enable_slider( + self.back_left_motor_slider, self.back_left_motor_label, self.value_change + ) + common_widgets.enable_slider( + self.back_right_motor_slider, self.back_right_motor_label, self.value_change + ) + + # Stop and Reset button for per-motor sliders + self.stop_and_reset_per_motor = QPushButton("Stop and Reset") + self.stop_and_reset_per_motor.clicked.connect(self.reset_motor_sliders) + + # Adding layouts to the box + dbox.addLayout(fl_layout) + dbox.addLayout(fr_layout) + dbox.addLayout(bl_layout) + dbox.addLayout(br_layout) + dbox.addWidget( + self.stop_and_reset_per_motor, alignment=Qt.AlignmentFlag.AlignCenter + ) + + group_box.setLayout(dbox) + + return group_box + def setup_dribbler(self, title: str) -> QGroupBox: """Create a widget to control the dribbler RPM @@ -159,74 +284,113 @@ def setup_dribbler(self, title: str) -> QGroupBox: return group_box - def toggle_all(self, enable: bool) -> None: - """Disables or enables all sliders and buttons depending on boolean parameter + def toggle_control_mode(self, use_direct: bool) -> None: + """Switches between 'Direct Velocity' and 'Per Motor' control modes. - Updates listener functions and stylesheets accordingly + :param use_direct: True to enable Direct Velocity mode, False to enable Per Motor mode. + """ + # reset sliders + self.reset_motor_sliders() + self.reset_direct_sliders() + self.disconnect_direct_sliders() + self.disconnect_motor_sliders() + + if use_direct: + # Show the direct velocity widget + self.drive_widget.setCurrentWidget(self.direct_velocity_widget) + + # Enable direct sliders and disable motor sliders + common_widgets.enable_slider( + self.x_velocity_slider, self.x_velocity_label, self.value_change + ) + common_widgets.enable_slider( + self.y_velocity_slider, self.y_velocity_label, self.value_change + ) + common_widgets.enable_slider( + self.angular_velocity_slider, self.angular_velocity_label, self.value_change + ) + + common_widgets.disable_slider(self.front_left_motor_slider) + common_widgets.disable_slider(self.front_right_motor_slider) + common_widgets.disable_slider(self.back_left_motor_slider) + common_widgets.disable_slider(self.back_right_motor_slider) + + common_widgets.change_button_state(self.stop_and_reset_direct, True) + common_widgets.change_button_state(self.use_direct_velocity, True) + common_widgets.change_button_state(self.stop_and_reset_per_motor, False) + common_widgets.change_button_state(self.use_per_motor, False) - :param enable: boolean parameter, True is enable and False is disable + else: + # Show the per motor widget + self.drive_widget.setCurrentWidget(self.per_motor_widget) + + # Enable motor sliders and disable direct sliders + common_widgets.enable_slider( + self.front_left_motor_slider, self.front_left_motor_label, self.value_change + ) + common_widgets.enable_slider( + self.front_right_motor_slider, self.front_right_motor_label, self.value_change + ) + common_widgets.enable_slider( + self.back_left_motor_slider, self.back_left_motor_label, self.value_change + ) + common_widgets.enable_slider( + self.back_right_motor_slider, self.back_right_motor_label, self.value_change + ) + + common_widgets.disable_slider(self.x_velocity_slider) + common_widgets.disable_slider(self.y_velocity_slider) + common_widgets.disable_slider(self.angular_velocity_slider) + + common_widgets.change_button_state(self.stop_and_reset_per_motor, True) + common_widgets.change_button_state(self.use_per_motor, True) + common_widgets.change_button_state(self.stop_and_reset_direct, False) + common_widgets.change_button_state(self.use_direct_velocity, False) + + def toggle_dribbler_sliders(self, enable: bool) -> None: + """Enables or disables dribbler sliders. + + :param enable: True to enable, False to disable """ if enable: - if not self.enabled: - # disconnect all sliders - self.disconnect_sliders() - - # enable all sliders by adding listener to update label with slider value - common_widgets.enable_slider( - self.x_velocity_slider, self.x_velocity_label, self.value_change - ) - common_widgets.enable_slider( - self.y_velocity_slider, self.y_velocity_label, self.value_change - ) - common_widgets.enable_slider( - self.angular_velocity_slider, - self.angular_velocity_label, - self.value_change, - ) - common_widgets.enable_slider( - self.dribbler_speed_rpm_slider, - self.dribbler_speed_rpm_label, - self.value_change, - ) - - # enable buttons - common_widgets.change_button_state(self.stop_and_reset_dribbler, True) - common_widgets.change_button_state(self.stop_and_reset_direct, True) - - # change enabled field - self.enabled = True + common_widgets.enable_slider( + self.dribbler_speed_rpm_slider, + self.dribbler_speed_rpm_label, + self.value_change, + ) + common_widgets.change_button_state(self.stop_and_reset_dribbler, True) else: - if self.enabled: - # reset slider values and disconnect - self.reset_all_sliders() - self.disconnect_sliders() - - # disable all sliders by adding listener to keep slider value the same - common_widgets.disable_slider(self.x_velocity_slider) - common_widgets.disable_slider(self.y_velocity_slider) - common_widgets.disable_slider(self.angular_velocity_slider) - common_widgets.disable_slider(self.dribbler_speed_rpm_slider) - - # disable buttons - common_widgets.change_button_state(self.stop_and_reset_dribbler, False) - common_widgets.change_button_state(self.stop_and_reset_direct, False) - - # change enabled field - self.enabled = False - - def disconnect_sliders(self) -> None: - """Disconnect listener for changing values for all sliders""" + common_widgets.disable_slider(self.dribbler_speed_rpm_slider) + common_widgets.change_button_state(self.stop_and_reset_dribbler, False) + + def disconnect_direct_sliders(self) -> None: + """Disconnect listener for changing values for motor sliders""" self.x_velocity_slider.valueChanged.disconnect() self.y_velocity_slider.valueChanged.disconnect() self.angular_velocity_slider.valueChanged.disconnect() + + def disconnect_dribbler_sliders(self) -> None: self.dribbler_speed_rpm_slider.valueChanged.disconnect() + def disconnect_motor_sliders(self) -> None: + self.front_left_motor_slider.valueChanged.disconnect() + self.front_right_motor_slider.valueChanged.disconnect() + self.back_left_motor_slider.valueChanged.disconnect() + self.back_right_motor_slider.valueChanged.disconnect() + def reset_direct_sliders(self) -> None: """Reset direct sliders back to 0""" self.x_velocity_slider.setValue(0) self.y_velocity_slider.setValue(0) self.angular_velocity_slider.setValue(0) + def reset_motor_sliders(self) -> None: + """Reset direct sliders back to 0""" + self.front_left_motor_slider.setValue(0) + self.front_right_motor_slider.setValue(0) + self.back_left_motor_slider.setValue(0) + self.back_right_motor_slider.setValue(0) + def reset_dribbler_slider(self) -> None: """Reset the dribbler slider back to 0""" self.dribbler_speed_rpm_slider.setValue(0) @@ -234,4 +398,5 @@ def reset_dribbler_slider(self) -> None: def reset_all_sliders(self) -> None: """Reset all sliders back to 0""" self.reset_direct_sliders() + self.reset_motor_sliders() self.reset_dribbler_slider() From d0eb031feeb8263fcfb324ac2a00b8c825c77811 Mon Sep 17 00:00:00 2001 From: Muk Date: Sat, 26 Oct 2024 15:21:33 -0700 Subject: [PATCH 02/17] Progress so far --- .../drive_and_dribbler_widget.py | 80 ++++++++++++------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py index c8360f61ab..a3f5c65a16 100644 --- a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py +++ b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py @@ -5,6 +5,14 @@ from software.thunderscope.common import common_widgets from proto.import_all_protos import * from software.thunderscope.proto_unix_io import ProtoUnixIO +from enum import IntEnum + + +class DriveMode(IntEnum): + """Enum for the 2 drive modes (direct velocity and per-motor)""" + + VELOCITY = 0 + MOTOR = 1 class DriveAndDribblerWidget(QWidget): @@ -28,11 +36,12 @@ def __init__(self, proto_unix_io: ProtoUnixIO) -> None: self.drive_widget.addWidget(self.direct_velocity_widget) self.drive_widget.addWidget(self.per_motor_widget) - layout.addWidget(self.setup_drive_switch("Drive Mode Switch")) + layout.addWidget(self.setup_drive_switch_radio("Drive Mode Switch")) layout.addWidget(self.drive_widget) layout.addWidget(self.setup_dribbler("Dribbler")) self.enabled = True + self.per_motor = False self.setLayout(layout) self.toggle_control_mode(True) self.toggle_dribbler_sliders(True) @@ -42,16 +51,29 @@ def refresh(self) -> None: motor_control = MotorControl() motor_control.dribbler_speed_rpm = int(self.dribbler_speed_rpm_slider.value()) - motor_control.direct_velocity_control.velocity.x_component_meters = ( - self.x_velocity_slider.value() - ) - motor_control.direct_velocity_control.velocity.y_component_meters = ( - self.y_velocity_slider.value() - ) - motor_control.direct_velocity_control.angular_velocity.radians_per_second = ( - self.angular_velocity_slider.value() - ) - + if not self.per_motor: + motor_control.direct_velocity_control.velocity.x_component_meters = ( + self.x_velocity_slider.value() + ) + motor_control.direct_velocity_control.velocity.y_component_meters = ( + self.y_velocity_slider.value() + ) + motor_control.direct_velocity_control.angular_velocity.radians_per_second = ( + self.angular_velocity_slider.value() + ) + else: + motor_control.direct_per_wheel_control.front_left_wheel_velocity = ( + self.front_left_motor_slider.value() + ) + motor_control.direct_per_wheel_control.front_right_wheel_velocity = ( + self.front_right_motor_slider.value() + ) + motor_control.direct_per_wheel_control.back_left_wheel_velocity = ( + self.back_left_motor_slider.value() + ) + motor_control.direct_per_wheel_control.back_right_wheel_velocity = ( + self.back_right_motor_slider.value() + ) self.proto_unix_io.send_proto(MotorControl, motor_control) def value_change(self, value: float) -> str: @@ -63,28 +85,27 @@ def value_change(self, value: float) -> str: value_str = "%.2f" % value return value_str - def setup_drive_switch(self, title: str) -> QGroupBox: - """Create a widget to switch between per-motor and velocity control modes + def setup_drive_switch_radio(self, title: str) -> QGroupBox: + """Create a radio button widget to switch between per-motor and velocity control modes :param title: group box name """ group_box = QGroupBox(title) dbox = QVBoxLayout() - - # Each button disables itself after being pressed. - self.use_per_motor = QPushButton("Per-Motor") - self.use_per_motor.clicked.connect(self.switch_to_motor) - - self.use_direct_velocity = QPushButton("Velocity Control") - self.use_direct_velocity.clicked.connect(self.switch_to_velocity) - - dbox.addWidget( - self.use_direct_velocity, alignment=Qt.AlignmentFlag.AlignCenter + self.connect_options_group = QButtonGroup() + radio_button_names = ["Velocity Control", "Per Motor Control"] + self.connect_options_box, self.connect_options = common_widgets.create_radio( + radio_button_names, self.connect_options_group ) - dbox.addWidget( - self.use_per_motor, alignment=Qt.AlignmentFlag.AlignCenter + self.use_direct_velocity = self.connect_options[DriveMode.VELOCITY] + self.use_per_motor = self.connect_options[DriveMode.MOTOR] + self.use_direct_velocity.clicked.connect( + self.switch_to_velocity ) - + self.use_per_motor.clicked.connect( + self.switch_to_motor + ) + dbox.addWidget(self.connect_options_box) group_box.setLayout(dbox) return group_box @@ -167,6 +188,7 @@ def setup_per_motor(self, title: str) -> QGroupBox: :param title: the name of the group box """ + group_box = QGroupBox(title) dbox = QVBoxLayout() @@ -297,6 +319,7 @@ def toggle_control_mode(self, use_direct: bool) -> None: if use_direct: # Show the direct velocity widget + self.per_motor = False self.drive_widget.setCurrentWidget(self.direct_velocity_widget) # Enable direct sliders and disable motor sliders @@ -316,11 +339,10 @@ def toggle_control_mode(self, use_direct: bool) -> None: common_widgets.disable_slider(self.back_right_motor_slider) common_widgets.change_button_state(self.stop_and_reset_direct, True) - common_widgets.change_button_state(self.use_direct_velocity, True) common_widgets.change_button_state(self.stop_and_reset_per_motor, False) - common_widgets.change_button_state(self.use_per_motor, False) else: + self.per_motor = True # Show the per motor widget self.drive_widget.setCurrentWidget(self.per_motor_widget) @@ -343,9 +365,7 @@ def toggle_control_mode(self, use_direct: bool) -> None: common_widgets.disable_slider(self.angular_velocity_slider) common_widgets.change_button_state(self.stop_and_reset_per_motor, True) - common_widgets.change_button_state(self.use_per_motor, True) common_widgets.change_button_state(self.stop_and_reset_direct, False) - common_widgets.change_button_state(self.use_direct_velocity, False) def toggle_dribbler_sliders(self, enable: bool) -> None: """Enables or disables dribbler sliders. From 61282d33f960f1b593f1cc917e4467657e21fcca Mon Sep 17 00:00:00 2001 From: Muk Date: Sat, 26 Oct 2024 15:56:52 -0700 Subject: [PATCH 03/17] per motor control works on robot --- .../robot_diagnostics/drive_and_dribbler_widget.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py index a3f5c65a16..4a0d2d66bb 100644 --- a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py +++ b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py @@ -52,6 +52,7 @@ def refresh(self) -> None: motor_control.dribbler_speed_rpm = int(self.dribbler_speed_rpm_slider.value()) if not self.per_motor: + motor_control.ClearField("direct_per_wheel_control") motor_control.direct_velocity_control.velocity.x_component_meters = ( self.x_velocity_slider.value() ) @@ -62,6 +63,7 @@ def refresh(self) -> None: self.angular_velocity_slider.value() ) else: + motor_control.ClearField("direct_velocity_control") motor_control.direct_per_wheel_control.front_left_wheel_velocity = ( self.front_left_motor_slider.value() ) @@ -74,6 +76,7 @@ def refresh(self) -> None: motor_control.direct_per_wheel_control.back_right_wheel_velocity = ( self.back_right_motor_slider.value() ) + self.proto_unix_io.send_proto(MotorControl, motor_control) def value_change(self, value: float) -> str: From 2b88ee68a0a266e5e01447616a4509f08fb35821 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 26 Oct 2024 23:41:09 +0000 Subject: [PATCH 04/17] [pre-commit.ci lite] apply automatic fixes --- .../drive_and_dribbler_widget.py | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py index 4a0d2d66bb..233e092716 100644 --- a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py +++ b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py @@ -31,7 +31,9 @@ def __init__(self, proto_unix_io: ProtoUnixIO) -> None: self.proto_unix_io = proto_unix_io # create swappable widget system using stacked widgets - self.direct_velocity_widget = self.setup_direct_velocity("Drive - Direct Velocity") + self.direct_velocity_widget = self.setup_direct_velocity( + "Drive - Direct Velocity" + ) self.per_motor_widget = self.setup_per_motor("Drive - Per Motor") self.drive_widget.addWidget(self.direct_velocity_widget) self.drive_widget.addWidget(self.per_motor_widget) @@ -59,9 +61,7 @@ def refresh(self) -> None: motor_control.direct_velocity_control.velocity.y_component_meters = ( self.y_velocity_slider.value() ) - motor_control.direct_velocity_control.angular_velocity.radians_per_second = ( - self.angular_velocity_slider.value() - ) + motor_control.direct_velocity_control.angular_velocity.radians_per_second = self.angular_velocity_slider.value() else: motor_control.ClearField("direct_velocity_control") motor_control.direct_per_wheel_control.front_left_wheel_velocity = ( @@ -102,12 +102,8 @@ def setup_drive_switch_radio(self, title: str) -> QGroupBox: ) self.use_direct_velocity = self.connect_options[DriveMode.VELOCITY] self.use_per_motor = self.connect_options[DriveMode.MOTOR] - self.use_direct_velocity.clicked.connect( - self.switch_to_velocity - ) - self.use_per_motor.clicked.connect( - self.switch_to_motor - ) + self.use_direct_velocity.clicked.connect(self.switch_to_velocity) + self.use_per_motor.clicked.connect(self.switch_to_motor) dbox.addWidget(self.connect_options_box) group_box.setLayout(dbox) @@ -191,7 +187,6 @@ def setup_per_motor(self, title: str) -> QGroupBox: :param title: the name of the group box """ - group_box = QGroupBox(title) dbox = QVBoxLayout() @@ -245,7 +240,9 @@ def setup_per_motor(self, title: str) -> QGroupBox: self.front_left_motor_slider, self.front_left_motor_label, self.value_change ) common_widgets.enable_slider( - self.front_right_motor_slider, self.front_right_motor_label, self.value_change + self.front_right_motor_slider, + self.front_right_motor_label, + self.value_change, ) common_widgets.enable_slider( self.back_left_motor_slider, self.back_left_motor_label, self.value_change @@ -333,7 +330,9 @@ def toggle_control_mode(self, use_direct: bool) -> None: self.y_velocity_slider, self.y_velocity_label, self.value_change ) common_widgets.enable_slider( - self.angular_velocity_slider, self.angular_velocity_label, self.value_change + self.angular_velocity_slider, + self.angular_velocity_label, + self.value_change, ) common_widgets.disable_slider(self.front_left_motor_slider) @@ -351,16 +350,24 @@ def toggle_control_mode(self, use_direct: bool) -> None: # Enable motor sliders and disable direct sliders common_widgets.enable_slider( - self.front_left_motor_slider, self.front_left_motor_label, self.value_change + self.front_left_motor_slider, + self.front_left_motor_label, + self.value_change, ) common_widgets.enable_slider( - self.front_right_motor_slider, self.front_right_motor_label, self.value_change + self.front_right_motor_slider, + self.front_right_motor_label, + self.value_change, ) common_widgets.enable_slider( - self.back_left_motor_slider, self.back_left_motor_label, self.value_change + self.back_left_motor_slider, + self.back_left_motor_label, + self.value_change, ) common_widgets.enable_slider( - self.back_right_motor_slider, self.back_right_motor_label, self.value_change + self.back_right_motor_slider, + self.back_right_motor_label, + self.value_change, ) common_widgets.disable_slider(self.x_velocity_slider) From 7aa12bda1ee88a6c5e2c4470127ffdc59c52c813 Mon Sep 17 00:00:00 2001 From: Muk Date: Sat, 2 Nov 2024 16:09:13 -0700 Subject: [PATCH 05/17] per motor updated based on comments --- .../drive_and_dribbler_widget.py | 53 ++++++++----------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py index 4a0d2d66bb..ab0bc80320 100644 --- a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py +++ b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py @@ -10,7 +10,6 @@ class DriveMode(IntEnum): """Enum for the 2 drive modes (direct velocity and per-motor)""" - VELOCITY = 0 MOTOR = 1 @@ -40,10 +39,10 @@ def __init__(self, proto_unix_io: ProtoUnixIO) -> None: layout.addWidget(self.drive_widget) layout.addWidget(self.setup_dribbler("Dribbler")) - self.enabled = True - self.per_motor = False + self.drive_mode = DriveMode.MOTOR + self.use_direct_velocity.setChecked(True) self.setLayout(layout) - self.toggle_control_mode(True) + self.toggle_drive_mode(True) self.toggle_dribbler_sliders(True) def refresh(self) -> None: @@ -51,29 +50,29 @@ def refresh(self) -> None: motor_control = MotorControl() motor_control.dribbler_speed_rpm = int(self.dribbler_speed_rpm_slider.value()) - if not self.per_motor: + if not self.drive_mode == DriveMode.VELOCITY: motor_control.ClearField("direct_per_wheel_control") - motor_control.direct_velocity_control.velocity.x_component_meters = ( + motor_control.direct_velocity_drive.velocity.x_component_meters = ( self.x_velocity_slider.value() ) - motor_control.direct_velocity_control.velocity.y_component_meters = ( + motor_control.direct_velocity_drive.velocity.y_component_meters = ( self.y_velocity_slider.value() ) - motor_control.direct_velocity_control.angular_velocity.radians_per_second = ( + motor_control.direct_velocity_drive.angular_velocity.radians_per_second = ( self.angular_velocity_slider.value() ) else: motor_control.ClearField("direct_velocity_control") - motor_control.direct_per_wheel_control.front_left_wheel_velocity = ( + motor_control.direct_per_wheel_drive.front_left_wheel_velocity = ( self.front_left_motor_slider.value() ) - motor_control.direct_per_wheel_control.front_right_wheel_velocity = ( + motor_control.direct_per_wheel_drive.front_right_wheel_velocity = ( self.front_right_motor_slider.value() ) - motor_control.direct_per_wheel_control.back_left_wheel_velocity = ( + motor_control.direct_per_wheel_drive.back_left_wheel_velocity = ( self.back_left_motor_slider.value() ) - motor_control.direct_per_wheel_control.back_right_wheel_velocity = ( + motor_control.direct_per_wheel_drive.back_right_wheel_velocity = ( self.back_right_motor_slider.value() ) @@ -89,12 +88,12 @@ def value_change(self, value: float) -> str: return value_str def setup_drive_switch_radio(self, title: str) -> QGroupBox: - """Create a radio button widget to switch between per-motor and velocity control modes + """Create a radio button widget to switch between per-motor and velocity drive modes - :param title: group box name + :param title: vbox name """ group_box = QGroupBox(title) - dbox = QVBoxLayout() + vbox = QVBoxLayout() self.connect_options_group = QButtonGroup() radio_button_names = ["Velocity Control", "Per Motor Control"] self.connect_options_box, self.connect_options = common_widgets.create_radio( @@ -103,22 +102,16 @@ def setup_drive_switch_radio(self, title: str) -> QGroupBox: self.use_direct_velocity = self.connect_options[DriveMode.VELOCITY] self.use_per_motor = self.connect_options[DriveMode.MOTOR] self.use_direct_velocity.clicked.connect( - self.switch_to_velocity + lambda: self.toggle_drive_mode(True) ) self.use_per_motor.clicked.connect( - self.switch_to_motor + lambda: self.toggle_drive_mode(False) ) - dbox.addWidget(self.connect_options_box) - group_box.setLayout(dbox) + vbox.addWidget(self.connect_options_box) + group_box.setLayout(vbox) return group_box - def switch_to_velocity(self): - self.toggle_control_mode(True) - - def switch_to_motor(self): - self.toggle_control_mode(False) - def setup_direct_velocity(self, title: str) -> QGroupBox: """Create a widget to control the direct velocity of the robot's motors @@ -309,10 +302,10 @@ def setup_dribbler(self, title: str) -> QGroupBox: return group_box - def toggle_control_mode(self, use_direct: bool) -> None: - """Switches between 'Direct Velocity' and 'Per Motor' control modes. + def toggle_drive_mode(self, use_drive_mode: IntEnum) -> None: + """Switches between 'Direct Velocity' and 'Per Motor' drive modes. - :param use_direct: True to enable Direct Velocity mode, False to enable Per Motor mode. + :param use_drive_mode: DriveMode.VELOCITY or DriveMode.MOTOR, switch to that mode. """ # reset sliders self.reset_motor_sliders() @@ -320,9 +313,8 @@ def toggle_control_mode(self, use_direct: bool) -> None: self.disconnect_direct_sliders() self.disconnect_motor_sliders() - if use_direct: + if use_drive_mode == DriveMode.VELOCITY: # Show the direct velocity widget - self.per_motor = False self.drive_widget.setCurrentWidget(self.direct_velocity_widget) # Enable direct sliders and disable motor sliders @@ -345,7 +337,6 @@ def toggle_control_mode(self, use_direct: bool) -> None: common_widgets.change_button_state(self.stop_and_reset_per_motor, False) else: - self.per_motor = True # Show the per motor widget self.drive_widget.setCurrentWidget(self.per_motor_widget) From 4e02b056fcff7f7143ff5843766d877e1c89391f Mon Sep 17 00:00:00 2001 From: Muk Date: Mon, 4 Nov 2024 01:45:50 -0800 Subject: [PATCH 06/17] per motor ready for second test --- .../drive_and_dribbler_widget.py | 10 +- .../drive_and_dribbler_widget_BACKUP_15256.py | 440 ++++++++++++++++++ .../drive_and_dribbler_widget_BASE_15256.py | 425 +++++++++++++++++ .../drive_and_dribbler_widget_LOCAL_15256.py | 416 +++++++++++++++++ .../drive_and_dribbler_widget_REMOTE_15256.py | 432 +++++++++++++++++ 5 files changed, 1719 insertions(+), 4 deletions(-) create mode 100644 src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_BACKUP_15256.py create mode 100644 src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_BASE_15256.py create mode 100644 src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_LOCAL_15256.py create mode 100644 src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_REMOTE_15256.py diff --git a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py index ab0bc80320..7ab773f8ee 100644 --- a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py +++ b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py @@ -39,10 +39,11 @@ def __init__(self, proto_unix_io: ProtoUnixIO) -> None: layout.addWidget(self.drive_widget) layout.addWidget(self.setup_dribbler("Dribbler")) - self.drive_mode = DriveMode.MOTOR + # default to direct velocity + self.drive_mode = DriveMode.VELOCITY self.use_direct_velocity.setChecked(True) + self.toggle_drive_mode(DriveMode.VELOCITY) self.setLayout(layout) - self.toggle_drive_mode(True) self.toggle_dribbler_sliders(True) def refresh(self) -> None: @@ -102,10 +103,10 @@ def setup_drive_switch_radio(self, title: str) -> QGroupBox: self.use_direct_velocity = self.connect_options[DriveMode.VELOCITY] self.use_per_motor = self.connect_options[DriveMode.MOTOR] self.use_direct_velocity.clicked.connect( - lambda: self.toggle_drive_mode(True) + lambda: self.toggle_drive_mode(DriveMode.VELOCITY) ) self.use_per_motor.clicked.connect( - lambda: self.toggle_drive_mode(False) + lambda: self.toggle_drive_mode(DriveMode.MOTOR) ) vbox.addWidget(self.connect_options_box) group_box.setLayout(vbox) @@ -314,6 +315,7 @@ def toggle_drive_mode(self, use_drive_mode: IntEnum) -> None: self.disconnect_motor_sliders() if use_drive_mode == DriveMode.VELOCITY: + # Show the direct velocity widget self.drive_widget.setCurrentWidget(self.direct_velocity_widget) diff --git a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_BACKUP_15256.py b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_BACKUP_15256.py new file mode 100644 index 0000000000..ac2f3e977c --- /dev/null +++ b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_BACKUP_15256.py @@ -0,0 +1,440 @@ +from pyqtgraph.Qt.QtCore import Qt +from pyqtgraph.Qt.QtWidgets import * +import time +import software.python_bindings as tbots_cpp +from software.thunderscope.common import common_widgets +from proto.import_all_protos import * +from software.thunderscope.proto_unix_io import ProtoUnixIO +from enum import IntEnum + + +class DriveMode(IntEnum): + """Enum for the 2 drive modes (direct velocity and per-motor)""" + VELOCITY = 0 + MOTOR = 1 + + +class DriveAndDribblerWidget(QWidget): + def __init__(self, proto_unix_io: ProtoUnixIO) -> None: + """Initialize the widget to control the robot's motors + + :param proto_unix_io: the proto_unix_io object + """ + self.input_a = time.time() + self.constants = tbots_cpp.create2021RobotConstants() + QWidget.__init__(self) + + layout = QVBoxLayout() + self.drive_widget = QStackedWidget() + + self.proto_unix_io = proto_unix_io + + # create swappable widget system using stacked widgets + self.direct_velocity_widget = self.setup_direct_velocity( + "Drive - Direct Velocity" + ) + self.per_motor_widget = self.setup_per_motor("Drive - Per Motor") + self.drive_widget.addWidget(self.direct_velocity_widget) + self.drive_widget.addWidget(self.per_motor_widget) + + layout.addWidget(self.setup_drive_switch_radio("Drive Mode Switch")) + layout.addWidget(self.drive_widget) + layout.addWidget(self.setup_dribbler("Dribbler")) + + self.drive_mode = DriveMode.MOTOR + self.use_direct_velocity.setChecked(True) + self.setLayout(layout) + self.toggle_drive_mode(True) + self.toggle_dribbler_sliders(True) + + def refresh(self) -> None: + """Refresh the widget and send the a MotorControl message with the current values""" + motor_control = MotorControl() + motor_control.dribbler_speed_rpm = int(self.dribbler_speed_rpm_slider.value()) + + if not self.drive_mode == DriveMode.VELOCITY: + motor_control.ClearField("direct_per_wheel_control") + motor_control.direct_velocity_drive.velocity.x_component_meters = ( + self.x_velocity_slider.value() + ) + motor_control.direct_velocity_drive.velocity.y_component_meters = ( + self.y_velocity_slider.value() + ) +<<<<<<< HEAD + motor_control.direct_velocity_drive.angular_velocity.radians_per_second = ( + self.angular_velocity_slider.value() + ) +======= + motor_control.direct_velocity_control.angular_velocity.radians_per_second = self.angular_velocity_slider.value() +>>>>>>> 2b88ee68a0a266e5e01447616a4509f08fb35821 + else: + motor_control.ClearField("direct_velocity_control") + motor_control.direct_per_wheel_drive.front_left_wheel_velocity = ( + self.front_left_motor_slider.value() + ) + motor_control.direct_per_wheel_drive.front_right_wheel_velocity = ( + self.front_right_motor_slider.value() + ) + motor_control.direct_per_wheel_drive.back_left_wheel_velocity = ( + self.back_left_motor_slider.value() + ) + motor_control.direct_per_wheel_drive.back_right_wheel_velocity = ( + self.back_right_motor_slider.value() + ) + + self.proto_unix_io.send_proto(MotorControl, motor_control) + + def value_change(self, value: float) -> str: + """Converts the given float value to a string label + + :param value: float value to be converted + """ + value = float(value) + value_str = "%.2f" % value + return value_str + + def setup_drive_switch_radio(self, title: str) -> QGroupBox: + """Create a radio button widget to switch between per-motor and velocity drive modes + + :param title: vbox name + """ + group_box = QGroupBox(title) + vbox = QVBoxLayout() + self.connect_options_group = QButtonGroup() + radio_button_names = ["Velocity Control", "Per Motor Control"] + self.connect_options_box, self.connect_options = common_widgets.create_radio( + radio_button_names, self.connect_options_group + ) + self.use_direct_velocity = self.connect_options[DriveMode.VELOCITY] + self.use_per_motor = self.connect_options[DriveMode.MOTOR] +<<<<<<< HEAD + self.use_direct_velocity.clicked.connect( + lambda: self.toggle_drive_mode(True) + ) + self.use_per_motor.clicked.connect( + lambda: self.toggle_drive_mode(False) + ) + vbox.addWidget(self.connect_options_box) + group_box.setLayout(vbox) +======= + self.use_direct_velocity.clicked.connect(self.switch_to_velocity) + self.use_per_motor.clicked.connect(self.switch_to_motor) + dbox.addWidget(self.connect_options_box) + group_box.setLayout(dbox) +>>>>>>> 2b88ee68a0a266e5e01447616a4509f08fb35821 + + return group_box + + def setup_direct_velocity(self, title: str) -> QGroupBox: + """Create a widget to control the direct velocity of the robot's motors + + :param title: the name of the slider + """ + group_box = QGroupBox(title) + dbox = QVBoxLayout() + + ( + x_layout, + self.x_velocity_slider, + self.x_velocity_label, + ) = common_widgets.create_float_slider( + "X (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + y_layout, + self.y_velocity_slider, + self.y_velocity_label, + ) = common_widgets.create_float_slider( + "Y (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + dps_layout, + self.angular_velocity_slider, + self.angular_velocity_label, + ) = common_widgets.create_float_slider( + "θ (rad/s)", + 2, + -self.constants.robot_max_ang_speed_rad_per_s, + self.constants.robot_max_ang_speed_rad_per_s, + 1, + ) + + # add listener functions for sliders to update label with slider value + common_widgets.enable_slider( + self.x_velocity_slider, self.x_velocity_label, self.value_change + ) + common_widgets.enable_slider( + self.y_velocity_slider, self.y_velocity_label, self.value_change + ) + common_widgets.enable_slider( + self.angular_velocity_slider, self.angular_velocity_label, self.value_change + ) + + self.stop_and_reset_direct = QPushButton("Stop and Reset") + self.stop_and_reset_direct.clicked.connect(self.reset_direct_sliders) + + dbox.addLayout(x_layout) + dbox.addLayout(y_layout) + dbox.addLayout(dps_layout) + dbox.addWidget( + self.stop_and_reset_direct, alignment=Qt.AlignmentFlag.AlignCenter + ) + + group_box.setLayout(dbox) + + return group_box + + def setup_per_motor(self, title: str) -> QGroupBox: + """Create a widget to control the rotation rate of each motor + + :param title: the name of the group box + """ + group_box = QGroupBox(title) + dbox = QVBoxLayout() + + ( + fl_layout, + self.front_left_motor_slider, + self.front_left_motor_label, + ) = common_widgets.create_float_slider( + "Front Left Motor (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + fr_layout, + self.front_right_motor_slider, + self.front_right_motor_label, + ) = common_widgets.create_float_slider( + "Front Right Motor (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + bl_layout, + self.back_left_motor_slider, + self.back_left_motor_label, + ) = common_widgets.create_float_slider( + "Back Left Motor (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + br_layout, + self.back_right_motor_slider, + self.back_right_motor_label, + ) = common_widgets.create_float_slider( + "Back Right Motor (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + + # Add listener functions for each motor's slider to update labels with slider values + common_widgets.enable_slider( + self.front_left_motor_slider, self.front_left_motor_label, self.value_change + ) + common_widgets.enable_slider( + self.front_right_motor_slider, + self.front_right_motor_label, + self.value_change, + ) + common_widgets.enable_slider( + self.back_left_motor_slider, self.back_left_motor_label, self.value_change + ) + common_widgets.enable_slider( + self.back_right_motor_slider, self.back_right_motor_label, self.value_change + ) + + # Stop and Reset button for per-motor sliders + self.stop_and_reset_per_motor = QPushButton("Stop and Reset") + self.stop_and_reset_per_motor.clicked.connect(self.reset_motor_sliders) + + # Adding layouts to the box + dbox.addLayout(fl_layout) + dbox.addLayout(fr_layout) + dbox.addLayout(bl_layout) + dbox.addLayout(br_layout) + dbox.addWidget( + self.stop_and_reset_per_motor, alignment=Qt.AlignmentFlag.AlignCenter + ) + + group_box.setLayout(dbox) + + return group_box + + def setup_dribbler(self, title: str) -> QGroupBox: + """Create a widget to control the dribbler RPM + + :param title: the name of the slider + """ + group_box = QGroupBox(title) + dbox = QVBoxLayout() + + ( + dribbler_layout, + self.dribbler_speed_rpm_slider, + self.dribbler_speed_rpm_label, + ) = common_widgets.create_float_slider( + "RPM", + 1, + self.constants.indefinite_dribbler_speed_rpm, + -self.constants.indefinite_dribbler_speed_rpm, + 1, + ) + + # add listener function to update label with slider value + common_widgets.enable_slider( + self.dribbler_speed_rpm_slider, + self.dribbler_speed_rpm_label, + self.value_change, + ) + + self.stop_and_reset_dribbler = QPushButton("Stop and Reset") + self.stop_and_reset_dribbler.clicked.connect(self.reset_dribbler_slider) + + dbox.addLayout(dribbler_layout) + dbox.addWidget( + self.stop_and_reset_dribbler, alignment=Qt.AlignmentFlag.AlignCenter + ) + group_box.setLayout(dbox) + + return group_box + + def toggle_drive_mode(self, use_drive_mode: IntEnum) -> None: + """Switches between 'Direct Velocity' and 'Per Motor' drive modes. + + :param use_drive_mode: DriveMode.VELOCITY or DriveMode.MOTOR, switch to that mode. + """ + # reset sliders + self.reset_motor_sliders() + self.reset_direct_sliders() + self.disconnect_direct_sliders() + self.disconnect_motor_sliders() + + if use_drive_mode == DriveMode.VELOCITY: + # Show the direct velocity widget + self.drive_widget.setCurrentWidget(self.direct_velocity_widget) + + # Enable direct sliders and disable motor sliders + common_widgets.enable_slider( + self.x_velocity_slider, self.x_velocity_label, self.value_change + ) + common_widgets.enable_slider( + self.y_velocity_slider, self.y_velocity_label, self.value_change + ) + common_widgets.enable_slider( + self.angular_velocity_slider, + self.angular_velocity_label, + self.value_change, + ) + + common_widgets.disable_slider(self.front_left_motor_slider) + common_widgets.disable_slider(self.front_right_motor_slider) + common_widgets.disable_slider(self.back_left_motor_slider) + common_widgets.disable_slider(self.back_right_motor_slider) + + common_widgets.change_button_state(self.stop_and_reset_direct, True) + common_widgets.change_button_state(self.stop_and_reset_per_motor, False) + + else: + # Show the per motor widget + self.drive_widget.setCurrentWidget(self.per_motor_widget) + + # Enable motor sliders and disable direct sliders + common_widgets.enable_slider( + self.front_left_motor_slider, + self.front_left_motor_label, + self.value_change, + ) + common_widgets.enable_slider( + self.front_right_motor_slider, + self.front_right_motor_label, + self.value_change, + ) + common_widgets.enable_slider( + self.back_left_motor_slider, + self.back_left_motor_label, + self.value_change, + ) + common_widgets.enable_slider( + self.back_right_motor_slider, + self.back_right_motor_label, + self.value_change, + ) + + common_widgets.disable_slider(self.x_velocity_slider) + common_widgets.disable_slider(self.y_velocity_slider) + common_widgets.disable_slider(self.angular_velocity_slider) + + common_widgets.change_button_state(self.stop_and_reset_per_motor, True) + common_widgets.change_button_state(self.stop_and_reset_direct, False) + + def toggle_dribbler_sliders(self, enable: bool) -> None: + """Enables or disables dribbler sliders. + + :param enable: True to enable, False to disable + """ + if enable: + common_widgets.enable_slider( + self.dribbler_speed_rpm_slider, + self.dribbler_speed_rpm_label, + self.value_change, + ) + common_widgets.change_button_state(self.stop_and_reset_dribbler, True) + else: + common_widgets.disable_slider(self.dribbler_speed_rpm_slider) + common_widgets.change_button_state(self.stop_and_reset_dribbler, False) + + def disconnect_direct_sliders(self) -> None: + """Disconnect listener for changing values for motor sliders""" + self.x_velocity_slider.valueChanged.disconnect() + self.y_velocity_slider.valueChanged.disconnect() + self.angular_velocity_slider.valueChanged.disconnect() + + def disconnect_dribbler_sliders(self) -> None: + self.dribbler_speed_rpm_slider.valueChanged.disconnect() + + def disconnect_motor_sliders(self) -> None: + self.front_left_motor_slider.valueChanged.disconnect() + self.front_right_motor_slider.valueChanged.disconnect() + self.back_left_motor_slider.valueChanged.disconnect() + self.back_right_motor_slider.valueChanged.disconnect() + + def reset_direct_sliders(self) -> None: + """Reset direct sliders back to 0""" + self.x_velocity_slider.setValue(0) + self.y_velocity_slider.setValue(0) + self.angular_velocity_slider.setValue(0) + + def reset_motor_sliders(self) -> None: + """Reset direct sliders back to 0""" + self.front_left_motor_slider.setValue(0) + self.front_right_motor_slider.setValue(0) + self.back_left_motor_slider.setValue(0) + self.back_right_motor_slider.setValue(0) + + def reset_dribbler_slider(self) -> None: + """Reset the dribbler slider back to 0""" + self.dribbler_speed_rpm_slider.setValue(0) + + def reset_all_sliders(self) -> None: + """Reset all sliders back to 0""" + self.reset_direct_sliders() + self.reset_motor_sliders() + self.reset_dribbler_slider() diff --git a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_BASE_15256.py b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_BASE_15256.py new file mode 100644 index 0000000000..4a0d2d66bb --- /dev/null +++ b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_BASE_15256.py @@ -0,0 +1,425 @@ +from pyqtgraph.Qt.QtCore import Qt +from pyqtgraph.Qt.QtWidgets import * +import time +import software.python_bindings as tbots_cpp +from software.thunderscope.common import common_widgets +from proto.import_all_protos import * +from software.thunderscope.proto_unix_io import ProtoUnixIO +from enum import IntEnum + + +class DriveMode(IntEnum): + """Enum for the 2 drive modes (direct velocity and per-motor)""" + + VELOCITY = 0 + MOTOR = 1 + + +class DriveAndDribblerWidget(QWidget): + def __init__(self, proto_unix_io: ProtoUnixIO) -> None: + """Initialize the widget to control the robot's motors + + :param proto_unix_io: the proto_unix_io object + """ + self.input_a = time.time() + self.constants = tbots_cpp.create2021RobotConstants() + QWidget.__init__(self) + + layout = QVBoxLayout() + self.drive_widget = QStackedWidget() + + self.proto_unix_io = proto_unix_io + + # create swappable widget system using stacked widgets + self.direct_velocity_widget = self.setup_direct_velocity("Drive - Direct Velocity") + self.per_motor_widget = self.setup_per_motor("Drive - Per Motor") + self.drive_widget.addWidget(self.direct_velocity_widget) + self.drive_widget.addWidget(self.per_motor_widget) + + layout.addWidget(self.setup_drive_switch_radio("Drive Mode Switch")) + layout.addWidget(self.drive_widget) + layout.addWidget(self.setup_dribbler("Dribbler")) + + self.enabled = True + self.per_motor = False + self.setLayout(layout) + self.toggle_control_mode(True) + self.toggle_dribbler_sliders(True) + + def refresh(self) -> None: + """Refresh the widget and send the a MotorControl message with the current values""" + motor_control = MotorControl() + motor_control.dribbler_speed_rpm = int(self.dribbler_speed_rpm_slider.value()) + + if not self.per_motor: + motor_control.ClearField("direct_per_wheel_control") + motor_control.direct_velocity_control.velocity.x_component_meters = ( + self.x_velocity_slider.value() + ) + motor_control.direct_velocity_control.velocity.y_component_meters = ( + self.y_velocity_slider.value() + ) + motor_control.direct_velocity_control.angular_velocity.radians_per_second = ( + self.angular_velocity_slider.value() + ) + else: + motor_control.ClearField("direct_velocity_control") + motor_control.direct_per_wheel_control.front_left_wheel_velocity = ( + self.front_left_motor_slider.value() + ) + motor_control.direct_per_wheel_control.front_right_wheel_velocity = ( + self.front_right_motor_slider.value() + ) + motor_control.direct_per_wheel_control.back_left_wheel_velocity = ( + self.back_left_motor_slider.value() + ) + motor_control.direct_per_wheel_control.back_right_wheel_velocity = ( + self.back_right_motor_slider.value() + ) + + self.proto_unix_io.send_proto(MotorControl, motor_control) + + def value_change(self, value: float) -> str: + """Converts the given float value to a string label + + :param value: float value to be converted + """ + value = float(value) + value_str = "%.2f" % value + return value_str + + def setup_drive_switch_radio(self, title: str) -> QGroupBox: + """Create a radio button widget to switch between per-motor and velocity control modes + + :param title: group box name + """ + group_box = QGroupBox(title) + dbox = QVBoxLayout() + self.connect_options_group = QButtonGroup() + radio_button_names = ["Velocity Control", "Per Motor Control"] + self.connect_options_box, self.connect_options = common_widgets.create_radio( + radio_button_names, self.connect_options_group + ) + self.use_direct_velocity = self.connect_options[DriveMode.VELOCITY] + self.use_per_motor = self.connect_options[DriveMode.MOTOR] + self.use_direct_velocity.clicked.connect( + self.switch_to_velocity + ) + self.use_per_motor.clicked.connect( + self.switch_to_motor + ) + dbox.addWidget(self.connect_options_box) + group_box.setLayout(dbox) + + return group_box + + def switch_to_velocity(self): + self.toggle_control_mode(True) + + def switch_to_motor(self): + self.toggle_control_mode(False) + + def setup_direct_velocity(self, title: str) -> QGroupBox: + """Create a widget to control the direct velocity of the robot's motors + + :param title: the name of the slider + """ + group_box = QGroupBox(title) + dbox = QVBoxLayout() + + ( + x_layout, + self.x_velocity_slider, + self.x_velocity_label, + ) = common_widgets.create_float_slider( + "X (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + y_layout, + self.y_velocity_slider, + self.y_velocity_label, + ) = common_widgets.create_float_slider( + "Y (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + dps_layout, + self.angular_velocity_slider, + self.angular_velocity_label, + ) = common_widgets.create_float_slider( + "θ (rad/s)", + 2, + -self.constants.robot_max_ang_speed_rad_per_s, + self.constants.robot_max_ang_speed_rad_per_s, + 1, + ) + + # add listener functions for sliders to update label with slider value + common_widgets.enable_slider( + self.x_velocity_slider, self.x_velocity_label, self.value_change + ) + common_widgets.enable_slider( + self.y_velocity_slider, self.y_velocity_label, self.value_change + ) + common_widgets.enable_slider( + self.angular_velocity_slider, self.angular_velocity_label, self.value_change + ) + + self.stop_and_reset_direct = QPushButton("Stop and Reset") + self.stop_and_reset_direct.clicked.connect(self.reset_direct_sliders) + + dbox.addLayout(x_layout) + dbox.addLayout(y_layout) + dbox.addLayout(dps_layout) + dbox.addWidget( + self.stop_and_reset_direct, alignment=Qt.AlignmentFlag.AlignCenter + ) + + group_box.setLayout(dbox) + + return group_box + + def setup_per_motor(self, title: str) -> QGroupBox: + """Create a widget to control the rotation rate of each motor + + :param title: the name of the group box + """ + + group_box = QGroupBox(title) + dbox = QVBoxLayout() + + ( + fl_layout, + self.front_left_motor_slider, + self.front_left_motor_label, + ) = common_widgets.create_float_slider( + "Front Left Motor (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + fr_layout, + self.front_right_motor_slider, + self.front_right_motor_label, + ) = common_widgets.create_float_slider( + "Front Right Motor (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + bl_layout, + self.back_left_motor_slider, + self.back_left_motor_label, + ) = common_widgets.create_float_slider( + "Back Left Motor (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + br_layout, + self.back_right_motor_slider, + self.back_right_motor_label, + ) = common_widgets.create_float_slider( + "Back Right Motor (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + + # Add listener functions for each motor's slider to update labels with slider values + common_widgets.enable_slider( + self.front_left_motor_slider, self.front_left_motor_label, self.value_change + ) + common_widgets.enable_slider( + self.front_right_motor_slider, self.front_right_motor_label, self.value_change + ) + common_widgets.enable_slider( + self.back_left_motor_slider, self.back_left_motor_label, self.value_change + ) + common_widgets.enable_slider( + self.back_right_motor_slider, self.back_right_motor_label, self.value_change + ) + + # Stop and Reset button for per-motor sliders + self.stop_and_reset_per_motor = QPushButton("Stop and Reset") + self.stop_and_reset_per_motor.clicked.connect(self.reset_motor_sliders) + + # Adding layouts to the box + dbox.addLayout(fl_layout) + dbox.addLayout(fr_layout) + dbox.addLayout(bl_layout) + dbox.addLayout(br_layout) + dbox.addWidget( + self.stop_and_reset_per_motor, alignment=Qt.AlignmentFlag.AlignCenter + ) + + group_box.setLayout(dbox) + + return group_box + + def setup_dribbler(self, title: str) -> QGroupBox: + """Create a widget to control the dribbler RPM + + :param title: the name of the slider + """ + group_box = QGroupBox(title) + dbox = QVBoxLayout() + + ( + dribbler_layout, + self.dribbler_speed_rpm_slider, + self.dribbler_speed_rpm_label, + ) = common_widgets.create_float_slider( + "RPM", + 1, + self.constants.indefinite_dribbler_speed_rpm, + -self.constants.indefinite_dribbler_speed_rpm, + 1, + ) + + # add listener function to update label with slider value + common_widgets.enable_slider( + self.dribbler_speed_rpm_slider, + self.dribbler_speed_rpm_label, + self.value_change, + ) + + self.stop_and_reset_dribbler = QPushButton("Stop and Reset") + self.stop_and_reset_dribbler.clicked.connect(self.reset_dribbler_slider) + + dbox.addLayout(dribbler_layout) + dbox.addWidget( + self.stop_and_reset_dribbler, alignment=Qt.AlignmentFlag.AlignCenter + ) + group_box.setLayout(dbox) + + return group_box + + def toggle_control_mode(self, use_direct: bool) -> None: + """Switches between 'Direct Velocity' and 'Per Motor' control modes. + + :param use_direct: True to enable Direct Velocity mode, False to enable Per Motor mode. + """ + # reset sliders + self.reset_motor_sliders() + self.reset_direct_sliders() + self.disconnect_direct_sliders() + self.disconnect_motor_sliders() + + if use_direct: + # Show the direct velocity widget + self.per_motor = False + self.drive_widget.setCurrentWidget(self.direct_velocity_widget) + + # Enable direct sliders and disable motor sliders + common_widgets.enable_slider( + self.x_velocity_slider, self.x_velocity_label, self.value_change + ) + common_widgets.enable_slider( + self.y_velocity_slider, self.y_velocity_label, self.value_change + ) + common_widgets.enable_slider( + self.angular_velocity_slider, self.angular_velocity_label, self.value_change + ) + + common_widgets.disable_slider(self.front_left_motor_slider) + common_widgets.disable_slider(self.front_right_motor_slider) + common_widgets.disable_slider(self.back_left_motor_slider) + common_widgets.disable_slider(self.back_right_motor_slider) + + common_widgets.change_button_state(self.stop_and_reset_direct, True) + common_widgets.change_button_state(self.stop_and_reset_per_motor, False) + + else: + self.per_motor = True + # Show the per motor widget + self.drive_widget.setCurrentWidget(self.per_motor_widget) + + # Enable motor sliders and disable direct sliders + common_widgets.enable_slider( + self.front_left_motor_slider, self.front_left_motor_label, self.value_change + ) + common_widgets.enable_slider( + self.front_right_motor_slider, self.front_right_motor_label, self.value_change + ) + common_widgets.enable_slider( + self.back_left_motor_slider, self.back_left_motor_label, self.value_change + ) + common_widgets.enable_slider( + self.back_right_motor_slider, self.back_right_motor_label, self.value_change + ) + + common_widgets.disable_slider(self.x_velocity_slider) + common_widgets.disable_slider(self.y_velocity_slider) + common_widgets.disable_slider(self.angular_velocity_slider) + + common_widgets.change_button_state(self.stop_and_reset_per_motor, True) + common_widgets.change_button_state(self.stop_and_reset_direct, False) + + def toggle_dribbler_sliders(self, enable: bool) -> None: + """Enables or disables dribbler sliders. + + :param enable: True to enable, False to disable + """ + if enable: + common_widgets.enable_slider( + self.dribbler_speed_rpm_slider, + self.dribbler_speed_rpm_label, + self.value_change, + ) + common_widgets.change_button_state(self.stop_and_reset_dribbler, True) + else: + common_widgets.disable_slider(self.dribbler_speed_rpm_slider) + common_widgets.change_button_state(self.stop_and_reset_dribbler, False) + + def disconnect_direct_sliders(self) -> None: + """Disconnect listener for changing values for motor sliders""" + self.x_velocity_slider.valueChanged.disconnect() + self.y_velocity_slider.valueChanged.disconnect() + self.angular_velocity_slider.valueChanged.disconnect() + + def disconnect_dribbler_sliders(self) -> None: + self.dribbler_speed_rpm_slider.valueChanged.disconnect() + + def disconnect_motor_sliders(self) -> None: + self.front_left_motor_slider.valueChanged.disconnect() + self.front_right_motor_slider.valueChanged.disconnect() + self.back_left_motor_slider.valueChanged.disconnect() + self.back_right_motor_slider.valueChanged.disconnect() + + def reset_direct_sliders(self) -> None: + """Reset direct sliders back to 0""" + self.x_velocity_slider.setValue(0) + self.y_velocity_slider.setValue(0) + self.angular_velocity_slider.setValue(0) + + def reset_motor_sliders(self) -> None: + """Reset direct sliders back to 0""" + self.front_left_motor_slider.setValue(0) + self.front_right_motor_slider.setValue(0) + self.back_left_motor_slider.setValue(0) + self.back_right_motor_slider.setValue(0) + + def reset_dribbler_slider(self) -> None: + """Reset the dribbler slider back to 0""" + self.dribbler_speed_rpm_slider.setValue(0) + + def reset_all_sliders(self) -> None: + """Reset all sliders back to 0""" + self.reset_direct_sliders() + self.reset_motor_sliders() + self.reset_dribbler_slider() diff --git a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_LOCAL_15256.py b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_LOCAL_15256.py new file mode 100644 index 0000000000..ab0bc80320 --- /dev/null +++ b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_LOCAL_15256.py @@ -0,0 +1,416 @@ +from pyqtgraph.Qt.QtCore import Qt +from pyqtgraph.Qt.QtWidgets import * +import time +import software.python_bindings as tbots_cpp +from software.thunderscope.common import common_widgets +from proto.import_all_protos import * +from software.thunderscope.proto_unix_io import ProtoUnixIO +from enum import IntEnum + + +class DriveMode(IntEnum): + """Enum for the 2 drive modes (direct velocity and per-motor)""" + VELOCITY = 0 + MOTOR = 1 + + +class DriveAndDribblerWidget(QWidget): + def __init__(self, proto_unix_io: ProtoUnixIO) -> None: + """Initialize the widget to control the robot's motors + + :param proto_unix_io: the proto_unix_io object + """ + self.input_a = time.time() + self.constants = tbots_cpp.create2021RobotConstants() + QWidget.__init__(self) + + layout = QVBoxLayout() + self.drive_widget = QStackedWidget() + + self.proto_unix_io = proto_unix_io + + # create swappable widget system using stacked widgets + self.direct_velocity_widget = self.setup_direct_velocity("Drive - Direct Velocity") + self.per_motor_widget = self.setup_per_motor("Drive - Per Motor") + self.drive_widget.addWidget(self.direct_velocity_widget) + self.drive_widget.addWidget(self.per_motor_widget) + + layout.addWidget(self.setup_drive_switch_radio("Drive Mode Switch")) + layout.addWidget(self.drive_widget) + layout.addWidget(self.setup_dribbler("Dribbler")) + + self.drive_mode = DriveMode.MOTOR + self.use_direct_velocity.setChecked(True) + self.setLayout(layout) + self.toggle_drive_mode(True) + self.toggle_dribbler_sliders(True) + + def refresh(self) -> None: + """Refresh the widget and send the a MotorControl message with the current values""" + motor_control = MotorControl() + motor_control.dribbler_speed_rpm = int(self.dribbler_speed_rpm_slider.value()) + + if not self.drive_mode == DriveMode.VELOCITY: + motor_control.ClearField("direct_per_wheel_control") + motor_control.direct_velocity_drive.velocity.x_component_meters = ( + self.x_velocity_slider.value() + ) + motor_control.direct_velocity_drive.velocity.y_component_meters = ( + self.y_velocity_slider.value() + ) + motor_control.direct_velocity_drive.angular_velocity.radians_per_second = ( + self.angular_velocity_slider.value() + ) + else: + motor_control.ClearField("direct_velocity_control") + motor_control.direct_per_wheel_drive.front_left_wheel_velocity = ( + self.front_left_motor_slider.value() + ) + motor_control.direct_per_wheel_drive.front_right_wheel_velocity = ( + self.front_right_motor_slider.value() + ) + motor_control.direct_per_wheel_drive.back_left_wheel_velocity = ( + self.back_left_motor_slider.value() + ) + motor_control.direct_per_wheel_drive.back_right_wheel_velocity = ( + self.back_right_motor_slider.value() + ) + + self.proto_unix_io.send_proto(MotorControl, motor_control) + + def value_change(self, value: float) -> str: + """Converts the given float value to a string label + + :param value: float value to be converted + """ + value = float(value) + value_str = "%.2f" % value + return value_str + + def setup_drive_switch_radio(self, title: str) -> QGroupBox: + """Create a radio button widget to switch between per-motor and velocity drive modes + + :param title: vbox name + """ + group_box = QGroupBox(title) + vbox = QVBoxLayout() + self.connect_options_group = QButtonGroup() + radio_button_names = ["Velocity Control", "Per Motor Control"] + self.connect_options_box, self.connect_options = common_widgets.create_radio( + radio_button_names, self.connect_options_group + ) + self.use_direct_velocity = self.connect_options[DriveMode.VELOCITY] + self.use_per_motor = self.connect_options[DriveMode.MOTOR] + self.use_direct_velocity.clicked.connect( + lambda: self.toggle_drive_mode(True) + ) + self.use_per_motor.clicked.connect( + lambda: self.toggle_drive_mode(False) + ) + vbox.addWidget(self.connect_options_box) + group_box.setLayout(vbox) + + return group_box + + def setup_direct_velocity(self, title: str) -> QGroupBox: + """Create a widget to control the direct velocity of the robot's motors + + :param title: the name of the slider + """ + group_box = QGroupBox(title) + dbox = QVBoxLayout() + + ( + x_layout, + self.x_velocity_slider, + self.x_velocity_label, + ) = common_widgets.create_float_slider( + "X (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + y_layout, + self.y_velocity_slider, + self.y_velocity_label, + ) = common_widgets.create_float_slider( + "Y (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + dps_layout, + self.angular_velocity_slider, + self.angular_velocity_label, + ) = common_widgets.create_float_slider( + "θ (rad/s)", + 2, + -self.constants.robot_max_ang_speed_rad_per_s, + self.constants.robot_max_ang_speed_rad_per_s, + 1, + ) + + # add listener functions for sliders to update label with slider value + common_widgets.enable_slider( + self.x_velocity_slider, self.x_velocity_label, self.value_change + ) + common_widgets.enable_slider( + self.y_velocity_slider, self.y_velocity_label, self.value_change + ) + common_widgets.enable_slider( + self.angular_velocity_slider, self.angular_velocity_label, self.value_change + ) + + self.stop_and_reset_direct = QPushButton("Stop and Reset") + self.stop_and_reset_direct.clicked.connect(self.reset_direct_sliders) + + dbox.addLayout(x_layout) + dbox.addLayout(y_layout) + dbox.addLayout(dps_layout) + dbox.addWidget( + self.stop_and_reset_direct, alignment=Qt.AlignmentFlag.AlignCenter + ) + + group_box.setLayout(dbox) + + return group_box + + def setup_per_motor(self, title: str) -> QGroupBox: + """Create a widget to control the rotation rate of each motor + + :param title: the name of the group box + """ + + group_box = QGroupBox(title) + dbox = QVBoxLayout() + + ( + fl_layout, + self.front_left_motor_slider, + self.front_left_motor_label, + ) = common_widgets.create_float_slider( + "Front Left Motor (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + fr_layout, + self.front_right_motor_slider, + self.front_right_motor_label, + ) = common_widgets.create_float_slider( + "Front Right Motor (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + bl_layout, + self.back_left_motor_slider, + self.back_left_motor_label, + ) = common_widgets.create_float_slider( + "Back Left Motor (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + br_layout, + self.back_right_motor_slider, + self.back_right_motor_label, + ) = common_widgets.create_float_slider( + "Back Right Motor (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + + # Add listener functions for each motor's slider to update labels with slider values + common_widgets.enable_slider( + self.front_left_motor_slider, self.front_left_motor_label, self.value_change + ) + common_widgets.enable_slider( + self.front_right_motor_slider, self.front_right_motor_label, self.value_change + ) + common_widgets.enable_slider( + self.back_left_motor_slider, self.back_left_motor_label, self.value_change + ) + common_widgets.enable_slider( + self.back_right_motor_slider, self.back_right_motor_label, self.value_change + ) + + # Stop and Reset button for per-motor sliders + self.stop_and_reset_per_motor = QPushButton("Stop and Reset") + self.stop_and_reset_per_motor.clicked.connect(self.reset_motor_sliders) + + # Adding layouts to the box + dbox.addLayout(fl_layout) + dbox.addLayout(fr_layout) + dbox.addLayout(bl_layout) + dbox.addLayout(br_layout) + dbox.addWidget( + self.stop_and_reset_per_motor, alignment=Qt.AlignmentFlag.AlignCenter + ) + + group_box.setLayout(dbox) + + return group_box + + def setup_dribbler(self, title: str) -> QGroupBox: + """Create a widget to control the dribbler RPM + + :param title: the name of the slider + """ + group_box = QGroupBox(title) + dbox = QVBoxLayout() + + ( + dribbler_layout, + self.dribbler_speed_rpm_slider, + self.dribbler_speed_rpm_label, + ) = common_widgets.create_float_slider( + "RPM", + 1, + self.constants.indefinite_dribbler_speed_rpm, + -self.constants.indefinite_dribbler_speed_rpm, + 1, + ) + + # add listener function to update label with slider value + common_widgets.enable_slider( + self.dribbler_speed_rpm_slider, + self.dribbler_speed_rpm_label, + self.value_change, + ) + + self.stop_and_reset_dribbler = QPushButton("Stop and Reset") + self.stop_and_reset_dribbler.clicked.connect(self.reset_dribbler_slider) + + dbox.addLayout(dribbler_layout) + dbox.addWidget( + self.stop_and_reset_dribbler, alignment=Qt.AlignmentFlag.AlignCenter + ) + group_box.setLayout(dbox) + + return group_box + + def toggle_drive_mode(self, use_drive_mode: IntEnum) -> None: + """Switches between 'Direct Velocity' and 'Per Motor' drive modes. + + :param use_drive_mode: DriveMode.VELOCITY or DriveMode.MOTOR, switch to that mode. + """ + # reset sliders + self.reset_motor_sliders() + self.reset_direct_sliders() + self.disconnect_direct_sliders() + self.disconnect_motor_sliders() + + if use_drive_mode == DriveMode.VELOCITY: + # Show the direct velocity widget + self.drive_widget.setCurrentWidget(self.direct_velocity_widget) + + # Enable direct sliders and disable motor sliders + common_widgets.enable_slider( + self.x_velocity_slider, self.x_velocity_label, self.value_change + ) + common_widgets.enable_slider( + self.y_velocity_slider, self.y_velocity_label, self.value_change + ) + common_widgets.enable_slider( + self.angular_velocity_slider, self.angular_velocity_label, self.value_change + ) + + common_widgets.disable_slider(self.front_left_motor_slider) + common_widgets.disable_slider(self.front_right_motor_slider) + common_widgets.disable_slider(self.back_left_motor_slider) + common_widgets.disable_slider(self.back_right_motor_slider) + + common_widgets.change_button_state(self.stop_and_reset_direct, True) + common_widgets.change_button_state(self.stop_and_reset_per_motor, False) + + else: + # Show the per motor widget + self.drive_widget.setCurrentWidget(self.per_motor_widget) + + # Enable motor sliders and disable direct sliders + common_widgets.enable_slider( + self.front_left_motor_slider, self.front_left_motor_label, self.value_change + ) + common_widgets.enable_slider( + self.front_right_motor_slider, self.front_right_motor_label, self.value_change + ) + common_widgets.enable_slider( + self.back_left_motor_slider, self.back_left_motor_label, self.value_change + ) + common_widgets.enable_slider( + self.back_right_motor_slider, self.back_right_motor_label, self.value_change + ) + + common_widgets.disable_slider(self.x_velocity_slider) + common_widgets.disable_slider(self.y_velocity_slider) + common_widgets.disable_slider(self.angular_velocity_slider) + + common_widgets.change_button_state(self.stop_and_reset_per_motor, True) + common_widgets.change_button_state(self.stop_and_reset_direct, False) + + def toggle_dribbler_sliders(self, enable: bool) -> None: + """Enables or disables dribbler sliders. + + :param enable: True to enable, False to disable + """ + if enable: + common_widgets.enable_slider( + self.dribbler_speed_rpm_slider, + self.dribbler_speed_rpm_label, + self.value_change, + ) + common_widgets.change_button_state(self.stop_and_reset_dribbler, True) + else: + common_widgets.disable_slider(self.dribbler_speed_rpm_slider) + common_widgets.change_button_state(self.stop_and_reset_dribbler, False) + + def disconnect_direct_sliders(self) -> None: + """Disconnect listener for changing values for motor sliders""" + self.x_velocity_slider.valueChanged.disconnect() + self.y_velocity_slider.valueChanged.disconnect() + self.angular_velocity_slider.valueChanged.disconnect() + + def disconnect_dribbler_sliders(self) -> None: + self.dribbler_speed_rpm_slider.valueChanged.disconnect() + + def disconnect_motor_sliders(self) -> None: + self.front_left_motor_slider.valueChanged.disconnect() + self.front_right_motor_slider.valueChanged.disconnect() + self.back_left_motor_slider.valueChanged.disconnect() + self.back_right_motor_slider.valueChanged.disconnect() + + def reset_direct_sliders(self) -> None: + """Reset direct sliders back to 0""" + self.x_velocity_slider.setValue(0) + self.y_velocity_slider.setValue(0) + self.angular_velocity_slider.setValue(0) + + def reset_motor_sliders(self) -> None: + """Reset direct sliders back to 0""" + self.front_left_motor_slider.setValue(0) + self.front_right_motor_slider.setValue(0) + self.back_left_motor_slider.setValue(0) + self.back_right_motor_slider.setValue(0) + + def reset_dribbler_slider(self) -> None: + """Reset the dribbler slider back to 0""" + self.dribbler_speed_rpm_slider.setValue(0) + + def reset_all_sliders(self) -> None: + """Reset all sliders back to 0""" + self.reset_direct_sliders() + self.reset_motor_sliders() + self.reset_dribbler_slider() diff --git a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_REMOTE_15256.py b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_REMOTE_15256.py new file mode 100644 index 0000000000..233e092716 --- /dev/null +++ b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_REMOTE_15256.py @@ -0,0 +1,432 @@ +from pyqtgraph.Qt.QtCore import Qt +from pyqtgraph.Qt.QtWidgets import * +import time +import software.python_bindings as tbots_cpp +from software.thunderscope.common import common_widgets +from proto.import_all_protos import * +from software.thunderscope.proto_unix_io import ProtoUnixIO +from enum import IntEnum + + +class DriveMode(IntEnum): + """Enum for the 2 drive modes (direct velocity and per-motor)""" + + VELOCITY = 0 + MOTOR = 1 + + +class DriveAndDribblerWidget(QWidget): + def __init__(self, proto_unix_io: ProtoUnixIO) -> None: + """Initialize the widget to control the robot's motors + + :param proto_unix_io: the proto_unix_io object + """ + self.input_a = time.time() + self.constants = tbots_cpp.create2021RobotConstants() + QWidget.__init__(self) + + layout = QVBoxLayout() + self.drive_widget = QStackedWidget() + + self.proto_unix_io = proto_unix_io + + # create swappable widget system using stacked widgets + self.direct_velocity_widget = self.setup_direct_velocity( + "Drive - Direct Velocity" + ) + self.per_motor_widget = self.setup_per_motor("Drive - Per Motor") + self.drive_widget.addWidget(self.direct_velocity_widget) + self.drive_widget.addWidget(self.per_motor_widget) + + layout.addWidget(self.setup_drive_switch_radio("Drive Mode Switch")) + layout.addWidget(self.drive_widget) + layout.addWidget(self.setup_dribbler("Dribbler")) + + self.enabled = True + self.per_motor = False + self.setLayout(layout) + self.toggle_control_mode(True) + self.toggle_dribbler_sliders(True) + + def refresh(self) -> None: + """Refresh the widget and send the a MotorControl message with the current values""" + motor_control = MotorControl() + motor_control.dribbler_speed_rpm = int(self.dribbler_speed_rpm_slider.value()) + + if not self.per_motor: + motor_control.ClearField("direct_per_wheel_control") + motor_control.direct_velocity_control.velocity.x_component_meters = ( + self.x_velocity_slider.value() + ) + motor_control.direct_velocity_control.velocity.y_component_meters = ( + self.y_velocity_slider.value() + ) + motor_control.direct_velocity_control.angular_velocity.radians_per_second = self.angular_velocity_slider.value() + else: + motor_control.ClearField("direct_velocity_control") + motor_control.direct_per_wheel_control.front_left_wheel_velocity = ( + self.front_left_motor_slider.value() + ) + motor_control.direct_per_wheel_control.front_right_wheel_velocity = ( + self.front_right_motor_slider.value() + ) + motor_control.direct_per_wheel_control.back_left_wheel_velocity = ( + self.back_left_motor_slider.value() + ) + motor_control.direct_per_wheel_control.back_right_wheel_velocity = ( + self.back_right_motor_slider.value() + ) + + self.proto_unix_io.send_proto(MotorControl, motor_control) + + def value_change(self, value: float) -> str: + """Converts the given float value to a string label + + :param value: float value to be converted + """ + value = float(value) + value_str = "%.2f" % value + return value_str + + def setup_drive_switch_radio(self, title: str) -> QGroupBox: + """Create a radio button widget to switch between per-motor and velocity control modes + + :param title: group box name + """ + group_box = QGroupBox(title) + dbox = QVBoxLayout() + self.connect_options_group = QButtonGroup() + radio_button_names = ["Velocity Control", "Per Motor Control"] + self.connect_options_box, self.connect_options = common_widgets.create_radio( + radio_button_names, self.connect_options_group + ) + self.use_direct_velocity = self.connect_options[DriveMode.VELOCITY] + self.use_per_motor = self.connect_options[DriveMode.MOTOR] + self.use_direct_velocity.clicked.connect(self.switch_to_velocity) + self.use_per_motor.clicked.connect(self.switch_to_motor) + dbox.addWidget(self.connect_options_box) + group_box.setLayout(dbox) + + return group_box + + def switch_to_velocity(self): + self.toggle_control_mode(True) + + def switch_to_motor(self): + self.toggle_control_mode(False) + + def setup_direct_velocity(self, title: str) -> QGroupBox: + """Create a widget to control the direct velocity of the robot's motors + + :param title: the name of the slider + """ + group_box = QGroupBox(title) + dbox = QVBoxLayout() + + ( + x_layout, + self.x_velocity_slider, + self.x_velocity_label, + ) = common_widgets.create_float_slider( + "X (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + y_layout, + self.y_velocity_slider, + self.y_velocity_label, + ) = common_widgets.create_float_slider( + "Y (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + dps_layout, + self.angular_velocity_slider, + self.angular_velocity_label, + ) = common_widgets.create_float_slider( + "θ (rad/s)", + 2, + -self.constants.robot_max_ang_speed_rad_per_s, + self.constants.robot_max_ang_speed_rad_per_s, + 1, + ) + + # add listener functions for sliders to update label with slider value + common_widgets.enable_slider( + self.x_velocity_slider, self.x_velocity_label, self.value_change + ) + common_widgets.enable_slider( + self.y_velocity_slider, self.y_velocity_label, self.value_change + ) + common_widgets.enable_slider( + self.angular_velocity_slider, self.angular_velocity_label, self.value_change + ) + + self.stop_and_reset_direct = QPushButton("Stop and Reset") + self.stop_and_reset_direct.clicked.connect(self.reset_direct_sliders) + + dbox.addLayout(x_layout) + dbox.addLayout(y_layout) + dbox.addLayout(dps_layout) + dbox.addWidget( + self.stop_and_reset_direct, alignment=Qt.AlignmentFlag.AlignCenter + ) + + group_box.setLayout(dbox) + + return group_box + + def setup_per_motor(self, title: str) -> QGroupBox: + """Create a widget to control the rotation rate of each motor + + :param title: the name of the group box + """ + group_box = QGroupBox(title) + dbox = QVBoxLayout() + + ( + fl_layout, + self.front_left_motor_slider, + self.front_left_motor_label, + ) = common_widgets.create_float_slider( + "Front Left Motor (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + fr_layout, + self.front_right_motor_slider, + self.front_right_motor_label, + ) = common_widgets.create_float_slider( + "Front Right Motor (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + bl_layout, + self.back_left_motor_slider, + self.back_left_motor_label, + ) = common_widgets.create_float_slider( + "Back Left Motor (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + ( + br_layout, + self.back_right_motor_slider, + self.back_right_motor_label, + ) = common_widgets.create_float_slider( + "Back Right Motor (m/s)", + 2, + -self.constants.robot_max_speed_m_per_s, + self.constants.robot_max_speed_m_per_s, + 1, + ) + + # Add listener functions for each motor's slider to update labels with slider values + common_widgets.enable_slider( + self.front_left_motor_slider, self.front_left_motor_label, self.value_change + ) + common_widgets.enable_slider( + self.front_right_motor_slider, + self.front_right_motor_label, + self.value_change, + ) + common_widgets.enable_slider( + self.back_left_motor_slider, self.back_left_motor_label, self.value_change + ) + common_widgets.enable_slider( + self.back_right_motor_slider, self.back_right_motor_label, self.value_change + ) + + # Stop and Reset button for per-motor sliders + self.stop_and_reset_per_motor = QPushButton("Stop and Reset") + self.stop_and_reset_per_motor.clicked.connect(self.reset_motor_sliders) + + # Adding layouts to the box + dbox.addLayout(fl_layout) + dbox.addLayout(fr_layout) + dbox.addLayout(bl_layout) + dbox.addLayout(br_layout) + dbox.addWidget( + self.stop_and_reset_per_motor, alignment=Qt.AlignmentFlag.AlignCenter + ) + + group_box.setLayout(dbox) + + return group_box + + def setup_dribbler(self, title: str) -> QGroupBox: + """Create a widget to control the dribbler RPM + + :param title: the name of the slider + """ + group_box = QGroupBox(title) + dbox = QVBoxLayout() + + ( + dribbler_layout, + self.dribbler_speed_rpm_slider, + self.dribbler_speed_rpm_label, + ) = common_widgets.create_float_slider( + "RPM", + 1, + self.constants.indefinite_dribbler_speed_rpm, + -self.constants.indefinite_dribbler_speed_rpm, + 1, + ) + + # add listener function to update label with slider value + common_widgets.enable_slider( + self.dribbler_speed_rpm_slider, + self.dribbler_speed_rpm_label, + self.value_change, + ) + + self.stop_and_reset_dribbler = QPushButton("Stop and Reset") + self.stop_and_reset_dribbler.clicked.connect(self.reset_dribbler_slider) + + dbox.addLayout(dribbler_layout) + dbox.addWidget( + self.stop_and_reset_dribbler, alignment=Qt.AlignmentFlag.AlignCenter + ) + group_box.setLayout(dbox) + + return group_box + + def toggle_control_mode(self, use_direct: bool) -> None: + """Switches between 'Direct Velocity' and 'Per Motor' control modes. + + :param use_direct: True to enable Direct Velocity mode, False to enable Per Motor mode. + """ + # reset sliders + self.reset_motor_sliders() + self.reset_direct_sliders() + self.disconnect_direct_sliders() + self.disconnect_motor_sliders() + + if use_direct: + # Show the direct velocity widget + self.per_motor = False + self.drive_widget.setCurrentWidget(self.direct_velocity_widget) + + # Enable direct sliders and disable motor sliders + common_widgets.enable_slider( + self.x_velocity_slider, self.x_velocity_label, self.value_change + ) + common_widgets.enable_slider( + self.y_velocity_slider, self.y_velocity_label, self.value_change + ) + common_widgets.enable_slider( + self.angular_velocity_slider, + self.angular_velocity_label, + self.value_change, + ) + + common_widgets.disable_slider(self.front_left_motor_slider) + common_widgets.disable_slider(self.front_right_motor_slider) + common_widgets.disable_slider(self.back_left_motor_slider) + common_widgets.disable_slider(self.back_right_motor_slider) + + common_widgets.change_button_state(self.stop_and_reset_direct, True) + common_widgets.change_button_state(self.stop_and_reset_per_motor, False) + + else: + self.per_motor = True + # Show the per motor widget + self.drive_widget.setCurrentWidget(self.per_motor_widget) + + # Enable motor sliders and disable direct sliders + common_widgets.enable_slider( + self.front_left_motor_slider, + self.front_left_motor_label, + self.value_change, + ) + common_widgets.enable_slider( + self.front_right_motor_slider, + self.front_right_motor_label, + self.value_change, + ) + common_widgets.enable_slider( + self.back_left_motor_slider, + self.back_left_motor_label, + self.value_change, + ) + common_widgets.enable_slider( + self.back_right_motor_slider, + self.back_right_motor_label, + self.value_change, + ) + + common_widgets.disable_slider(self.x_velocity_slider) + common_widgets.disable_slider(self.y_velocity_slider) + common_widgets.disable_slider(self.angular_velocity_slider) + + common_widgets.change_button_state(self.stop_and_reset_per_motor, True) + common_widgets.change_button_state(self.stop_and_reset_direct, False) + + def toggle_dribbler_sliders(self, enable: bool) -> None: + """Enables or disables dribbler sliders. + + :param enable: True to enable, False to disable + """ + if enable: + common_widgets.enable_slider( + self.dribbler_speed_rpm_slider, + self.dribbler_speed_rpm_label, + self.value_change, + ) + common_widgets.change_button_state(self.stop_and_reset_dribbler, True) + else: + common_widgets.disable_slider(self.dribbler_speed_rpm_slider) + common_widgets.change_button_state(self.stop_and_reset_dribbler, False) + + def disconnect_direct_sliders(self) -> None: + """Disconnect listener for changing values for motor sliders""" + self.x_velocity_slider.valueChanged.disconnect() + self.y_velocity_slider.valueChanged.disconnect() + self.angular_velocity_slider.valueChanged.disconnect() + + def disconnect_dribbler_sliders(self) -> None: + self.dribbler_speed_rpm_slider.valueChanged.disconnect() + + def disconnect_motor_sliders(self) -> None: + self.front_left_motor_slider.valueChanged.disconnect() + self.front_right_motor_slider.valueChanged.disconnect() + self.back_left_motor_slider.valueChanged.disconnect() + self.back_right_motor_slider.valueChanged.disconnect() + + def reset_direct_sliders(self) -> None: + """Reset direct sliders back to 0""" + self.x_velocity_slider.setValue(0) + self.y_velocity_slider.setValue(0) + self.angular_velocity_slider.setValue(0) + + def reset_motor_sliders(self) -> None: + """Reset direct sliders back to 0""" + self.front_left_motor_slider.setValue(0) + self.front_right_motor_slider.setValue(0) + self.back_left_motor_slider.setValue(0) + self.back_right_motor_slider.setValue(0) + + def reset_dribbler_slider(self) -> None: + """Reset the dribbler slider back to 0""" + self.dribbler_speed_rpm_slider.setValue(0) + + def reset_all_sliders(self) -> None: + """Reset all sliders back to 0""" + self.reset_direct_sliders() + self.reset_motor_sliders() + self.reset_dribbler_slider() From 95c8b8a989c600979e07ba66aae8f283254cbe48 Mon Sep 17 00:00:00 2001 From: Muk Date: Mon, 4 Nov 2024 01:58:16 -0800 Subject: [PATCH 07/17] removed backups --- .../drive_and_dribbler_widget_BACKUP_15256.py | 440 ------------------ .../drive_and_dribbler_widget_BASE_15256.py | 425 ----------------- .../drive_and_dribbler_widget_LOCAL_15256.py | 416 ----------------- .../drive_and_dribbler_widget_REMOTE_15256.py | 432 ----------------- 4 files changed, 1713 deletions(-) delete mode 100644 src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_BACKUP_15256.py delete mode 100644 src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_BASE_15256.py delete mode 100644 src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_LOCAL_15256.py delete mode 100644 src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_REMOTE_15256.py diff --git a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_BACKUP_15256.py b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_BACKUP_15256.py deleted file mode 100644 index ac2f3e977c..0000000000 --- a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_BACKUP_15256.py +++ /dev/null @@ -1,440 +0,0 @@ -from pyqtgraph.Qt.QtCore import Qt -from pyqtgraph.Qt.QtWidgets import * -import time -import software.python_bindings as tbots_cpp -from software.thunderscope.common import common_widgets -from proto.import_all_protos import * -from software.thunderscope.proto_unix_io import ProtoUnixIO -from enum import IntEnum - - -class DriveMode(IntEnum): - """Enum for the 2 drive modes (direct velocity and per-motor)""" - VELOCITY = 0 - MOTOR = 1 - - -class DriveAndDribblerWidget(QWidget): - def __init__(self, proto_unix_io: ProtoUnixIO) -> None: - """Initialize the widget to control the robot's motors - - :param proto_unix_io: the proto_unix_io object - """ - self.input_a = time.time() - self.constants = tbots_cpp.create2021RobotConstants() - QWidget.__init__(self) - - layout = QVBoxLayout() - self.drive_widget = QStackedWidget() - - self.proto_unix_io = proto_unix_io - - # create swappable widget system using stacked widgets - self.direct_velocity_widget = self.setup_direct_velocity( - "Drive - Direct Velocity" - ) - self.per_motor_widget = self.setup_per_motor("Drive - Per Motor") - self.drive_widget.addWidget(self.direct_velocity_widget) - self.drive_widget.addWidget(self.per_motor_widget) - - layout.addWidget(self.setup_drive_switch_radio("Drive Mode Switch")) - layout.addWidget(self.drive_widget) - layout.addWidget(self.setup_dribbler("Dribbler")) - - self.drive_mode = DriveMode.MOTOR - self.use_direct_velocity.setChecked(True) - self.setLayout(layout) - self.toggle_drive_mode(True) - self.toggle_dribbler_sliders(True) - - def refresh(self) -> None: - """Refresh the widget and send the a MotorControl message with the current values""" - motor_control = MotorControl() - motor_control.dribbler_speed_rpm = int(self.dribbler_speed_rpm_slider.value()) - - if not self.drive_mode == DriveMode.VELOCITY: - motor_control.ClearField("direct_per_wheel_control") - motor_control.direct_velocity_drive.velocity.x_component_meters = ( - self.x_velocity_slider.value() - ) - motor_control.direct_velocity_drive.velocity.y_component_meters = ( - self.y_velocity_slider.value() - ) -<<<<<<< HEAD - motor_control.direct_velocity_drive.angular_velocity.radians_per_second = ( - self.angular_velocity_slider.value() - ) -======= - motor_control.direct_velocity_control.angular_velocity.radians_per_second = self.angular_velocity_slider.value() ->>>>>>> 2b88ee68a0a266e5e01447616a4509f08fb35821 - else: - motor_control.ClearField("direct_velocity_control") - motor_control.direct_per_wheel_drive.front_left_wheel_velocity = ( - self.front_left_motor_slider.value() - ) - motor_control.direct_per_wheel_drive.front_right_wheel_velocity = ( - self.front_right_motor_slider.value() - ) - motor_control.direct_per_wheel_drive.back_left_wheel_velocity = ( - self.back_left_motor_slider.value() - ) - motor_control.direct_per_wheel_drive.back_right_wheel_velocity = ( - self.back_right_motor_slider.value() - ) - - self.proto_unix_io.send_proto(MotorControl, motor_control) - - def value_change(self, value: float) -> str: - """Converts the given float value to a string label - - :param value: float value to be converted - """ - value = float(value) - value_str = "%.2f" % value - return value_str - - def setup_drive_switch_radio(self, title: str) -> QGroupBox: - """Create a radio button widget to switch between per-motor and velocity drive modes - - :param title: vbox name - """ - group_box = QGroupBox(title) - vbox = QVBoxLayout() - self.connect_options_group = QButtonGroup() - radio_button_names = ["Velocity Control", "Per Motor Control"] - self.connect_options_box, self.connect_options = common_widgets.create_radio( - radio_button_names, self.connect_options_group - ) - self.use_direct_velocity = self.connect_options[DriveMode.VELOCITY] - self.use_per_motor = self.connect_options[DriveMode.MOTOR] -<<<<<<< HEAD - self.use_direct_velocity.clicked.connect( - lambda: self.toggle_drive_mode(True) - ) - self.use_per_motor.clicked.connect( - lambda: self.toggle_drive_mode(False) - ) - vbox.addWidget(self.connect_options_box) - group_box.setLayout(vbox) -======= - self.use_direct_velocity.clicked.connect(self.switch_to_velocity) - self.use_per_motor.clicked.connect(self.switch_to_motor) - dbox.addWidget(self.connect_options_box) - group_box.setLayout(dbox) ->>>>>>> 2b88ee68a0a266e5e01447616a4509f08fb35821 - - return group_box - - def setup_direct_velocity(self, title: str) -> QGroupBox: - """Create a widget to control the direct velocity of the robot's motors - - :param title: the name of the slider - """ - group_box = QGroupBox(title) - dbox = QVBoxLayout() - - ( - x_layout, - self.x_velocity_slider, - self.x_velocity_label, - ) = common_widgets.create_float_slider( - "X (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - ( - y_layout, - self.y_velocity_slider, - self.y_velocity_label, - ) = common_widgets.create_float_slider( - "Y (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - ( - dps_layout, - self.angular_velocity_slider, - self.angular_velocity_label, - ) = common_widgets.create_float_slider( - "θ (rad/s)", - 2, - -self.constants.robot_max_ang_speed_rad_per_s, - self.constants.robot_max_ang_speed_rad_per_s, - 1, - ) - - # add listener functions for sliders to update label with slider value - common_widgets.enable_slider( - self.x_velocity_slider, self.x_velocity_label, self.value_change - ) - common_widgets.enable_slider( - self.y_velocity_slider, self.y_velocity_label, self.value_change - ) - common_widgets.enable_slider( - self.angular_velocity_slider, self.angular_velocity_label, self.value_change - ) - - self.stop_and_reset_direct = QPushButton("Stop and Reset") - self.stop_and_reset_direct.clicked.connect(self.reset_direct_sliders) - - dbox.addLayout(x_layout) - dbox.addLayout(y_layout) - dbox.addLayout(dps_layout) - dbox.addWidget( - self.stop_and_reset_direct, alignment=Qt.AlignmentFlag.AlignCenter - ) - - group_box.setLayout(dbox) - - return group_box - - def setup_per_motor(self, title: str) -> QGroupBox: - """Create a widget to control the rotation rate of each motor - - :param title: the name of the group box - """ - group_box = QGroupBox(title) - dbox = QVBoxLayout() - - ( - fl_layout, - self.front_left_motor_slider, - self.front_left_motor_label, - ) = common_widgets.create_float_slider( - "Front Left Motor (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - ( - fr_layout, - self.front_right_motor_slider, - self.front_right_motor_label, - ) = common_widgets.create_float_slider( - "Front Right Motor (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - ( - bl_layout, - self.back_left_motor_slider, - self.back_left_motor_label, - ) = common_widgets.create_float_slider( - "Back Left Motor (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - ( - br_layout, - self.back_right_motor_slider, - self.back_right_motor_label, - ) = common_widgets.create_float_slider( - "Back Right Motor (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - - # Add listener functions for each motor's slider to update labels with slider values - common_widgets.enable_slider( - self.front_left_motor_slider, self.front_left_motor_label, self.value_change - ) - common_widgets.enable_slider( - self.front_right_motor_slider, - self.front_right_motor_label, - self.value_change, - ) - common_widgets.enable_slider( - self.back_left_motor_slider, self.back_left_motor_label, self.value_change - ) - common_widgets.enable_slider( - self.back_right_motor_slider, self.back_right_motor_label, self.value_change - ) - - # Stop and Reset button for per-motor sliders - self.stop_and_reset_per_motor = QPushButton("Stop and Reset") - self.stop_and_reset_per_motor.clicked.connect(self.reset_motor_sliders) - - # Adding layouts to the box - dbox.addLayout(fl_layout) - dbox.addLayout(fr_layout) - dbox.addLayout(bl_layout) - dbox.addLayout(br_layout) - dbox.addWidget( - self.stop_and_reset_per_motor, alignment=Qt.AlignmentFlag.AlignCenter - ) - - group_box.setLayout(dbox) - - return group_box - - def setup_dribbler(self, title: str) -> QGroupBox: - """Create a widget to control the dribbler RPM - - :param title: the name of the slider - """ - group_box = QGroupBox(title) - dbox = QVBoxLayout() - - ( - dribbler_layout, - self.dribbler_speed_rpm_slider, - self.dribbler_speed_rpm_label, - ) = common_widgets.create_float_slider( - "RPM", - 1, - self.constants.indefinite_dribbler_speed_rpm, - -self.constants.indefinite_dribbler_speed_rpm, - 1, - ) - - # add listener function to update label with slider value - common_widgets.enable_slider( - self.dribbler_speed_rpm_slider, - self.dribbler_speed_rpm_label, - self.value_change, - ) - - self.stop_and_reset_dribbler = QPushButton("Stop and Reset") - self.stop_and_reset_dribbler.clicked.connect(self.reset_dribbler_slider) - - dbox.addLayout(dribbler_layout) - dbox.addWidget( - self.stop_and_reset_dribbler, alignment=Qt.AlignmentFlag.AlignCenter - ) - group_box.setLayout(dbox) - - return group_box - - def toggle_drive_mode(self, use_drive_mode: IntEnum) -> None: - """Switches between 'Direct Velocity' and 'Per Motor' drive modes. - - :param use_drive_mode: DriveMode.VELOCITY or DriveMode.MOTOR, switch to that mode. - """ - # reset sliders - self.reset_motor_sliders() - self.reset_direct_sliders() - self.disconnect_direct_sliders() - self.disconnect_motor_sliders() - - if use_drive_mode == DriveMode.VELOCITY: - # Show the direct velocity widget - self.drive_widget.setCurrentWidget(self.direct_velocity_widget) - - # Enable direct sliders and disable motor sliders - common_widgets.enable_slider( - self.x_velocity_slider, self.x_velocity_label, self.value_change - ) - common_widgets.enable_slider( - self.y_velocity_slider, self.y_velocity_label, self.value_change - ) - common_widgets.enable_slider( - self.angular_velocity_slider, - self.angular_velocity_label, - self.value_change, - ) - - common_widgets.disable_slider(self.front_left_motor_slider) - common_widgets.disable_slider(self.front_right_motor_slider) - common_widgets.disable_slider(self.back_left_motor_slider) - common_widgets.disable_slider(self.back_right_motor_slider) - - common_widgets.change_button_state(self.stop_and_reset_direct, True) - common_widgets.change_button_state(self.stop_and_reset_per_motor, False) - - else: - # Show the per motor widget - self.drive_widget.setCurrentWidget(self.per_motor_widget) - - # Enable motor sliders and disable direct sliders - common_widgets.enable_slider( - self.front_left_motor_slider, - self.front_left_motor_label, - self.value_change, - ) - common_widgets.enable_slider( - self.front_right_motor_slider, - self.front_right_motor_label, - self.value_change, - ) - common_widgets.enable_slider( - self.back_left_motor_slider, - self.back_left_motor_label, - self.value_change, - ) - common_widgets.enable_slider( - self.back_right_motor_slider, - self.back_right_motor_label, - self.value_change, - ) - - common_widgets.disable_slider(self.x_velocity_slider) - common_widgets.disable_slider(self.y_velocity_slider) - common_widgets.disable_slider(self.angular_velocity_slider) - - common_widgets.change_button_state(self.stop_and_reset_per_motor, True) - common_widgets.change_button_state(self.stop_and_reset_direct, False) - - def toggle_dribbler_sliders(self, enable: bool) -> None: - """Enables or disables dribbler sliders. - - :param enable: True to enable, False to disable - """ - if enable: - common_widgets.enable_slider( - self.dribbler_speed_rpm_slider, - self.dribbler_speed_rpm_label, - self.value_change, - ) - common_widgets.change_button_state(self.stop_and_reset_dribbler, True) - else: - common_widgets.disable_slider(self.dribbler_speed_rpm_slider) - common_widgets.change_button_state(self.stop_and_reset_dribbler, False) - - def disconnect_direct_sliders(self) -> None: - """Disconnect listener for changing values for motor sliders""" - self.x_velocity_slider.valueChanged.disconnect() - self.y_velocity_slider.valueChanged.disconnect() - self.angular_velocity_slider.valueChanged.disconnect() - - def disconnect_dribbler_sliders(self) -> None: - self.dribbler_speed_rpm_slider.valueChanged.disconnect() - - def disconnect_motor_sliders(self) -> None: - self.front_left_motor_slider.valueChanged.disconnect() - self.front_right_motor_slider.valueChanged.disconnect() - self.back_left_motor_slider.valueChanged.disconnect() - self.back_right_motor_slider.valueChanged.disconnect() - - def reset_direct_sliders(self) -> None: - """Reset direct sliders back to 0""" - self.x_velocity_slider.setValue(0) - self.y_velocity_slider.setValue(0) - self.angular_velocity_slider.setValue(0) - - def reset_motor_sliders(self) -> None: - """Reset direct sliders back to 0""" - self.front_left_motor_slider.setValue(0) - self.front_right_motor_slider.setValue(0) - self.back_left_motor_slider.setValue(0) - self.back_right_motor_slider.setValue(0) - - def reset_dribbler_slider(self) -> None: - """Reset the dribbler slider back to 0""" - self.dribbler_speed_rpm_slider.setValue(0) - - def reset_all_sliders(self) -> None: - """Reset all sliders back to 0""" - self.reset_direct_sliders() - self.reset_motor_sliders() - self.reset_dribbler_slider() diff --git a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_BASE_15256.py b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_BASE_15256.py deleted file mode 100644 index 4a0d2d66bb..0000000000 --- a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_BASE_15256.py +++ /dev/null @@ -1,425 +0,0 @@ -from pyqtgraph.Qt.QtCore import Qt -from pyqtgraph.Qt.QtWidgets import * -import time -import software.python_bindings as tbots_cpp -from software.thunderscope.common import common_widgets -from proto.import_all_protos import * -from software.thunderscope.proto_unix_io import ProtoUnixIO -from enum import IntEnum - - -class DriveMode(IntEnum): - """Enum for the 2 drive modes (direct velocity and per-motor)""" - - VELOCITY = 0 - MOTOR = 1 - - -class DriveAndDribblerWidget(QWidget): - def __init__(self, proto_unix_io: ProtoUnixIO) -> None: - """Initialize the widget to control the robot's motors - - :param proto_unix_io: the proto_unix_io object - """ - self.input_a = time.time() - self.constants = tbots_cpp.create2021RobotConstants() - QWidget.__init__(self) - - layout = QVBoxLayout() - self.drive_widget = QStackedWidget() - - self.proto_unix_io = proto_unix_io - - # create swappable widget system using stacked widgets - self.direct_velocity_widget = self.setup_direct_velocity("Drive - Direct Velocity") - self.per_motor_widget = self.setup_per_motor("Drive - Per Motor") - self.drive_widget.addWidget(self.direct_velocity_widget) - self.drive_widget.addWidget(self.per_motor_widget) - - layout.addWidget(self.setup_drive_switch_radio("Drive Mode Switch")) - layout.addWidget(self.drive_widget) - layout.addWidget(self.setup_dribbler("Dribbler")) - - self.enabled = True - self.per_motor = False - self.setLayout(layout) - self.toggle_control_mode(True) - self.toggle_dribbler_sliders(True) - - def refresh(self) -> None: - """Refresh the widget and send the a MotorControl message with the current values""" - motor_control = MotorControl() - motor_control.dribbler_speed_rpm = int(self.dribbler_speed_rpm_slider.value()) - - if not self.per_motor: - motor_control.ClearField("direct_per_wheel_control") - motor_control.direct_velocity_control.velocity.x_component_meters = ( - self.x_velocity_slider.value() - ) - motor_control.direct_velocity_control.velocity.y_component_meters = ( - self.y_velocity_slider.value() - ) - motor_control.direct_velocity_control.angular_velocity.radians_per_second = ( - self.angular_velocity_slider.value() - ) - else: - motor_control.ClearField("direct_velocity_control") - motor_control.direct_per_wheel_control.front_left_wheel_velocity = ( - self.front_left_motor_slider.value() - ) - motor_control.direct_per_wheel_control.front_right_wheel_velocity = ( - self.front_right_motor_slider.value() - ) - motor_control.direct_per_wheel_control.back_left_wheel_velocity = ( - self.back_left_motor_slider.value() - ) - motor_control.direct_per_wheel_control.back_right_wheel_velocity = ( - self.back_right_motor_slider.value() - ) - - self.proto_unix_io.send_proto(MotorControl, motor_control) - - def value_change(self, value: float) -> str: - """Converts the given float value to a string label - - :param value: float value to be converted - """ - value = float(value) - value_str = "%.2f" % value - return value_str - - def setup_drive_switch_radio(self, title: str) -> QGroupBox: - """Create a radio button widget to switch between per-motor and velocity control modes - - :param title: group box name - """ - group_box = QGroupBox(title) - dbox = QVBoxLayout() - self.connect_options_group = QButtonGroup() - radio_button_names = ["Velocity Control", "Per Motor Control"] - self.connect_options_box, self.connect_options = common_widgets.create_radio( - radio_button_names, self.connect_options_group - ) - self.use_direct_velocity = self.connect_options[DriveMode.VELOCITY] - self.use_per_motor = self.connect_options[DriveMode.MOTOR] - self.use_direct_velocity.clicked.connect( - self.switch_to_velocity - ) - self.use_per_motor.clicked.connect( - self.switch_to_motor - ) - dbox.addWidget(self.connect_options_box) - group_box.setLayout(dbox) - - return group_box - - def switch_to_velocity(self): - self.toggle_control_mode(True) - - def switch_to_motor(self): - self.toggle_control_mode(False) - - def setup_direct_velocity(self, title: str) -> QGroupBox: - """Create a widget to control the direct velocity of the robot's motors - - :param title: the name of the slider - """ - group_box = QGroupBox(title) - dbox = QVBoxLayout() - - ( - x_layout, - self.x_velocity_slider, - self.x_velocity_label, - ) = common_widgets.create_float_slider( - "X (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - ( - y_layout, - self.y_velocity_slider, - self.y_velocity_label, - ) = common_widgets.create_float_slider( - "Y (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - ( - dps_layout, - self.angular_velocity_slider, - self.angular_velocity_label, - ) = common_widgets.create_float_slider( - "θ (rad/s)", - 2, - -self.constants.robot_max_ang_speed_rad_per_s, - self.constants.robot_max_ang_speed_rad_per_s, - 1, - ) - - # add listener functions for sliders to update label with slider value - common_widgets.enable_slider( - self.x_velocity_slider, self.x_velocity_label, self.value_change - ) - common_widgets.enable_slider( - self.y_velocity_slider, self.y_velocity_label, self.value_change - ) - common_widgets.enable_slider( - self.angular_velocity_slider, self.angular_velocity_label, self.value_change - ) - - self.stop_and_reset_direct = QPushButton("Stop and Reset") - self.stop_and_reset_direct.clicked.connect(self.reset_direct_sliders) - - dbox.addLayout(x_layout) - dbox.addLayout(y_layout) - dbox.addLayout(dps_layout) - dbox.addWidget( - self.stop_and_reset_direct, alignment=Qt.AlignmentFlag.AlignCenter - ) - - group_box.setLayout(dbox) - - return group_box - - def setup_per_motor(self, title: str) -> QGroupBox: - """Create a widget to control the rotation rate of each motor - - :param title: the name of the group box - """ - - group_box = QGroupBox(title) - dbox = QVBoxLayout() - - ( - fl_layout, - self.front_left_motor_slider, - self.front_left_motor_label, - ) = common_widgets.create_float_slider( - "Front Left Motor (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - ( - fr_layout, - self.front_right_motor_slider, - self.front_right_motor_label, - ) = common_widgets.create_float_slider( - "Front Right Motor (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - ( - bl_layout, - self.back_left_motor_slider, - self.back_left_motor_label, - ) = common_widgets.create_float_slider( - "Back Left Motor (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - ( - br_layout, - self.back_right_motor_slider, - self.back_right_motor_label, - ) = common_widgets.create_float_slider( - "Back Right Motor (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - - # Add listener functions for each motor's slider to update labels with slider values - common_widgets.enable_slider( - self.front_left_motor_slider, self.front_left_motor_label, self.value_change - ) - common_widgets.enable_slider( - self.front_right_motor_slider, self.front_right_motor_label, self.value_change - ) - common_widgets.enable_slider( - self.back_left_motor_slider, self.back_left_motor_label, self.value_change - ) - common_widgets.enable_slider( - self.back_right_motor_slider, self.back_right_motor_label, self.value_change - ) - - # Stop and Reset button for per-motor sliders - self.stop_and_reset_per_motor = QPushButton("Stop and Reset") - self.stop_and_reset_per_motor.clicked.connect(self.reset_motor_sliders) - - # Adding layouts to the box - dbox.addLayout(fl_layout) - dbox.addLayout(fr_layout) - dbox.addLayout(bl_layout) - dbox.addLayout(br_layout) - dbox.addWidget( - self.stop_and_reset_per_motor, alignment=Qt.AlignmentFlag.AlignCenter - ) - - group_box.setLayout(dbox) - - return group_box - - def setup_dribbler(self, title: str) -> QGroupBox: - """Create a widget to control the dribbler RPM - - :param title: the name of the slider - """ - group_box = QGroupBox(title) - dbox = QVBoxLayout() - - ( - dribbler_layout, - self.dribbler_speed_rpm_slider, - self.dribbler_speed_rpm_label, - ) = common_widgets.create_float_slider( - "RPM", - 1, - self.constants.indefinite_dribbler_speed_rpm, - -self.constants.indefinite_dribbler_speed_rpm, - 1, - ) - - # add listener function to update label with slider value - common_widgets.enable_slider( - self.dribbler_speed_rpm_slider, - self.dribbler_speed_rpm_label, - self.value_change, - ) - - self.stop_and_reset_dribbler = QPushButton("Stop and Reset") - self.stop_and_reset_dribbler.clicked.connect(self.reset_dribbler_slider) - - dbox.addLayout(dribbler_layout) - dbox.addWidget( - self.stop_and_reset_dribbler, alignment=Qt.AlignmentFlag.AlignCenter - ) - group_box.setLayout(dbox) - - return group_box - - def toggle_control_mode(self, use_direct: bool) -> None: - """Switches between 'Direct Velocity' and 'Per Motor' control modes. - - :param use_direct: True to enable Direct Velocity mode, False to enable Per Motor mode. - """ - # reset sliders - self.reset_motor_sliders() - self.reset_direct_sliders() - self.disconnect_direct_sliders() - self.disconnect_motor_sliders() - - if use_direct: - # Show the direct velocity widget - self.per_motor = False - self.drive_widget.setCurrentWidget(self.direct_velocity_widget) - - # Enable direct sliders and disable motor sliders - common_widgets.enable_slider( - self.x_velocity_slider, self.x_velocity_label, self.value_change - ) - common_widgets.enable_slider( - self.y_velocity_slider, self.y_velocity_label, self.value_change - ) - common_widgets.enable_slider( - self.angular_velocity_slider, self.angular_velocity_label, self.value_change - ) - - common_widgets.disable_slider(self.front_left_motor_slider) - common_widgets.disable_slider(self.front_right_motor_slider) - common_widgets.disable_slider(self.back_left_motor_slider) - common_widgets.disable_slider(self.back_right_motor_slider) - - common_widgets.change_button_state(self.stop_and_reset_direct, True) - common_widgets.change_button_state(self.stop_and_reset_per_motor, False) - - else: - self.per_motor = True - # Show the per motor widget - self.drive_widget.setCurrentWidget(self.per_motor_widget) - - # Enable motor sliders and disable direct sliders - common_widgets.enable_slider( - self.front_left_motor_slider, self.front_left_motor_label, self.value_change - ) - common_widgets.enable_slider( - self.front_right_motor_slider, self.front_right_motor_label, self.value_change - ) - common_widgets.enable_slider( - self.back_left_motor_slider, self.back_left_motor_label, self.value_change - ) - common_widgets.enable_slider( - self.back_right_motor_slider, self.back_right_motor_label, self.value_change - ) - - common_widgets.disable_slider(self.x_velocity_slider) - common_widgets.disable_slider(self.y_velocity_slider) - common_widgets.disable_slider(self.angular_velocity_slider) - - common_widgets.change_button_state(self.stop_and_reset_per_motor, True) - common_widgets.change_button_state(self.stop_and_reset_direct, False) - - def toggle_dribbler_sliders(self, enable: bool) -> None: - """Enables or disables dribbler sliders. - - :param enable: True to enable, False to disable - """ - if enable: - common_widgets.enable_slider( - self.dribbler_speed_rpm_slider, - self.dribbler_speed_rpm_label, - self.value_change, - ) - common_widgets.change_button_state(self.stop_and_reset_dribbler, True) - else: - common_widgets.disable_slider(self.dribbler_speed_rpm_slider) - common_widgets.change_button_state(self.stop_and_reset_dribbler, False) - - def disconnect_direct_sliders(self) -> None: - """Disconnect listener for changing values for motor sliders""" - self.x_velocity_slider.valueChanged.disconnect() - self.y_velocity_slider.valueChanged.disconnect() - self.angular_velocity_slider.valueChanged.disconnect() - - def disconnect_dribbler_sliders(self) -> None: - self.dribbler_speed_rpm_slider.valueChanged.disconnect() - - def disconnect_motor_sliders(self) -> None: - self.front_left_motor_slider.valueChanged.disconnect() - self.front_right_motor_slider.valueChanged.disconnect() - self.back_left_motor_slider.valueChanged.disconnect() - self.back_right_motor_slider.valueChanged.disconnect() - - def reset_direct_sliders(self) -> None: - """Reset direct sliders back to 0""" - self.x_velocity_slider.setValue(0) - self.y_velocity_slider.setValue(0) - self.angular_velocity_slider.setValue(0) - - def reset_motor_sliders(self) -> None: - """Reset direct sliders back to 0""" - self.front_left_motor_slider.setValue(0) - self.front_right_motor_slider.setValue(0) - self.back_left_motor_slider.setValue(0) - self.back_right_motor_slider.setValue(0) - - def reset_dribbler_slider(self) -> None: - """Reset the dribbler slider back to 0""" - self.dribbler_speed_rpm_slider.setValue(0) - - def reset_all_sliders(self) -> None: - """Reset all sliders back to 0""" - self.reset_direct_sliders() - self.reset_motor_sliders() - self.reset_dribbler_slider() diff --git a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_LOCAL_15256.py b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_LOCAL_15256.py deleted file mode 100644 index ab0bc80320..0000000000 --- a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_LOCAL_15256.py +++ /dev/null @@ -1,416 +0,0 @@ -from pyqtgraph.Qt.QtCore import Qt -from pyqtgraph.Qt.QtWidgets import * -import time -import software.python_bindings as tbots_cpp -from software.thunderscope.common import common_widgets -from proto.import_all_protos import * -from software.thunderscope.proto_unix_io import ProtoUnixIO -from enum import IntEnum - - -class DriveMode(IntEnum): - """Enum for the 2 drive modes (direct velocity and per-motor)""" - VELOCITY = 0 - MOTOR = 1 - - -class DriveAndDribblerWidget(QWidget): - def __init__(self, proto_unix_io: ProtoUnixIO) -> None: - """Initialize the widget to control the robot's motors - - :param proto_unix_io: the proto_unix_io object - """ - self.input_a = time.time() - self.constants = tbots_cpp.create2021RobotConstants() - QWidget.__init__(self) - - layout = QVBoxLayout() - self.drive_widget = QStackedWidget() - - self.proto_unix_io = proto_unix_io - - # create swappable widget system using stacked widgets - self.direct_velocity_widget = self.setup_direct_velocity("Drive - Direct Velocity") - self.per_motor_widget = self.setup_per_motor("Drive - Per Motor") - self.drive_widget.addWidget(self.direct_velocity_widget) - self.drive_widget.addWidget(self.per_motor_widget) - - layout.addWidget(self.setup_drive_switch_radio("Drive Mode Switch")) - layout.addWidget(self.drive_widget) - layout.addWidget(self.setup_dribbler("Dribbler")) - - self.drive_mode = DriveMode.MOTOR - self.use_direct_velocity.setChecked(True) - self.setLayout(layout) - self.toggle_drive_mode(True) - self.toggle_dribbler_sliders(True) - - def refresh(self) -> None: - """Refresh the widget and send the a MotorControl message with the current values""" - motor_control = MotorControl() - motor_control.dribbler_speed_rpm = int(self.dribbler_speed_rpm_slider.value()) - - if not self.drive_mode == DriveMode.VELOCITY: - motor_control.ClearField("direct_per_wheel_control") - motor_control.direct_velocity_drive.velocity.x_component_meters = ( - self.x_velocity_slider.value() - ) - motor_control.direct_velocity_drive.velocity.y_component_meters = ( - self.y_velocity_slider.value() - ) - motor_control.direct_velocity_drive.angular_velocity.radians_per_second = ( - self.angular_velocity_slider.value() - ) - else: - motor_control.ClearField("direct_velocity_control") - motor_control.direct_per_wheel_drive.front_left_wheel_velocity = ( - self.front_left_motor_slider.value() - ) - motor_control.direct_per_wheel_drive.front_right_wheel_velocity = ( - self.front_right_motor_slider.value() - ) - motor_control.direct_per_wheel_drive.back_left_wheel_velocity = ( - self.back_left_motor_slider.value() - ) - motor_control.direct_per_wheel_drive.back_right_wheel_velocity = ( - self.back_right_motor_slider.value() - ) - - self.proto_unix_io.send_proto(MotorControl, motor_control) - - def value_change(self, value: float) -> str: - """Converts the given float value to a string label - - :param value: float value to be converted - """ - value = float(value) - value_str = "%.2f" % value - return value_str - - def setup_drive_switch_radio(self, title: str) -> QGroupBox: - """Create a radio button widget to switch between per-motor and velocity drive modes - - :param title: vbox name - """ - group_box = QGroupBox(title) - vbox = QVBoxLayout() - self.connect_options_group = QButtonGroup() - radio_button_names = ["Velocity Control", "Per Motor Control"] - self.connect_options_box, self.connect_options = common_widgets.create_radio( - radio_button_names, self.connect_options_group - ) - self.use_direct_velocity = self.connect_options[DriveMode.VELOCITY] - self.use_per_motor = self.connect_options[DriveMode.MOTOR] - self.use_direct_velocity.clicked.connect( - lambda: self.toggle_drive_mode(True) - ) - self.use_per_motor.clicked.connect( - lambda: self.toggle_drive_mode(False) - ) - vbox.addWidget(self.connect_options_box) - group_box.setLayout(vbox) - - return group_box - - def setup_direct_velocity(self, title: str) -> QGroupBox: - """Create a widget to control the direct velocity of the robot's motors - - :param title: the name of the slider - """ - group_box = QGroupBox(title) - dbox = QVBoxLayout() - - ( - x_layout, - self.x_velocity_slider, - self.x_velocity_label, - ) = common_widgets.create_float_slider( - "X (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - ( - y_layout, - self.y_velocity_slider, - self.y_velocity_label, - ) = common_widgets.create_float_slider( - "Y (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - ( - dps_layout, - self.angular_velocity_slider, - self.angular_velocity_label, - ) = common_widgets.create_float_slider( - "θ (rad/s)", - 2, - -self.constants.robot_max_ang_speed_rad_per_s, - self.constants.robot_max_ang_speed_rad_per_s, - 1, - ) - - # add listener functions for sliders to update label with slider value - common_widgets.enable_slider( - self.x_velocity_slider, self.x_velocity_label, self.value_change - ) - common_widgets.enable_slider( - self.y_velocity_slider, self.y_velocity_label, self.value_change - ) - common_widgets.enable_slider( - self.angular_velocity_slider, self.angular_velocity_label, self.value_change - ) - - self.stop_and_reset_direct = QPushButton("Stop and Reset") - self.stop_and_reset_direct.clicked.connect(self.reset_direct_sliders) - - dbox.addLayout(x_layout) - dbox.addLayout(y_layout) - dbox.addLayout(dps_layout) - dbox.addWidget( - self.stop_and_reset_direct, alignment=Qt.AlignmentFlag.AlignCenter - ) - - group_box.setLayout(dbox) - - return group_box - - def setup_per_motor(self, title: str) -> QGroupBox: - """Create a widget to control the rotation rate of each motor - - :param title: the name of the group box - """ - - group_box = QGroupBox(title) - dbox = QVBoxLayout() - - ( - fl_layout, - self.front_left_motor_slider, - self.front_left_motor_label, - ) = common_widgets.create_float_slider( - "Front Left Motor (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - ( - fr_layout, - self.front_right_motor_slider, - self.front_right_motor_label, - ) = common_widgets.create_float_slider( - "Front Right Motor (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - ( - bl_layout, - self.back_left_motor_slider, - self.back_left_motor_label, - ) = common_widgets.create_float_slider( - "Back Left Motor (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - ( - br_layout, - self.back_right_motor_slider, - self.back_right_motor_label, - ) = common_widgets.create_float_slider( - "Back Right Motor (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - - # Add listener functions for each motor's slider to update labels with slider values - common_widgets.enable_slider( - self.front_left_motor_slider, self.front_left_motor_label, self.value_change - ) - common_widgets.enable_slider( - self.front_right_motor_slider, self.front_right_motor_label, self.value_change - ) - common_widgets.enable_slider( - self.back_left_motor_slider, self.back_left_motor_label, self.value_change - ) - common_widgets.enable_slider( - self.back_right_motor_slider, self.back_right_motor_label, self.value_change - ) - - # Stop and Reset button for per-motor sliders - self.stop_and_reset_per_motor = QPushButton("Stop and Reset") - self.stop_and_reset_per_motor.clicked.connect(self.reset_motor_sliders) - - # Adding layouts to the box - dbox.addLayout(fl_layout) - dbox.addLayout(fr_layout) - dbox.addLayout(bl_layout) - dbox.addLayout(br_layout) - dbox.addWidget( - self.stop_and_reset_per_motor, alignment=Qt.AlignmentFlag.AlignCenter - ) - - group_box.setLayout(dbox) - - return group_box - - def setup_dribbler(self, title: str) -> QGroupBox: - """Create a widget to control the dribbler RPM - - :param title: the name of the slider - """ - group_box = QGroupBox(title) - dbox = QVBoxLayout() - - ( - dribbler_layout, - self.dribbler_speed_rpm_slider, - self.dribbler_speed_rpm_label, - ) = common_widgets.create_float_slider( - "RPM", - 1, - self.constants.indefinite_dribbler_speed_rpm, - -self.constants.indefinite_dribbler_speed_rpm, - 1, - ) - - # add listener function to update label with slider value - common_widgets.enable_slider( - self.dribbler_speed_rpm_slider, - self.dribbler_speed_rpm_label, - self.value_change, - ) - - self.stop_and_reset_dribbler = QPushButton("Stop and Reset") - self.stop_and_reset_dribbler.clicked.connect(self.reset_dribbler_slider) - - dbox.addLayout(dribbler_layout) - dbox.addWidget( - self.stop_and_reset_dribbler, alignment=Qt.AlignmentFlag.AlignCenter - ) - group_box.setLayout(dbox) - - return group_box - - def toggle_drive_mode(self, use_drive_mode: IntEnum) -> None: - """Switches between 'Direct Velocity' and 'Per Motor' drive modes. - - :param use_drive_mode: DriveMode.VELOCITY or DriveMode.MOTOR, switch to that mode. - """ - # reset sliders - self.reset_motor_sliders() - self.reset_direct_sliders() - self.disconnect_direct_sliders() - self.disconnect_motor_sliders() - - if use_drive_mode == DriveMode.VELOCITY: - # Show the direct velocity widget - self.drive_widget.setCurrentWidget(self.direct_velocity_widget) - - # Enable direct sliders and disable motor sliders - common_widgets.enable_slider( - self.x_velocity_slider, self.x_velocity_label, self.value_change - ) - common_widgets.enable_slider( - self.y_velocity_slider, self.y_velocity_label, self.value_change - ) - common_widgets.enable_slider( - self.angular_velocity_slider, self.angular_velocity_label, self.value_change - ) - - common_widgets.disable_slider(self.front_left_motor_slider) - common_widgets.disable_slider(self.front_right_motor_slider) - common_widgets.disable_slider(self.back_left_motor_slider) - common_widgets.disable_slider(self.back_right_motor_slider) - - common_widgets.change_button_state(self.stop_and_reset_direct, True) - common_widgets.change_button_state(self.stop_and_reset_per_motor, False) - - else: - # Show the per motor widget - self.drive_widget.setCurrentWidget(self.per_motor_widget) - - # Enable motor sliders and disable direct sliders - common_widgets.enable_slider( - self.front_left_motor_slider, self.front_left_motor_label, self.value_change - ) - common_widgets.enable_slider( - self.front_right_motor_slider, self.front_right_motor_label, self.value_change - ) - common_widgets.enable_slider( - self.back_left_motor_slider, self.back_left_motor_label, self.value_change - ) - common_widgets.enable_slider( - self.back_right_motor_slider, self.back_right_motor_label, self.value_change - ) - - common_widgets.disable_slider(self.x_velocity_slider) - common_widgets.disable_slider(self.y_velocity_slider) - common_widgets.disable_slider(self.angular_velocity_slider) - - common_widgets.change_button_state(self.stop_and_reset_per_motor, True) - common_widgets.change_button_state(self.stop_and_reset_direct, False) - - def toggle_dribbler_sliders(self, enable: bool) -> None: - """Enables or disables dribbler sliders. - - :param enable: True to enable, False to disable - """ - if enable: - common_widgets.enable_slider( - self.dribbler_speed_rpm_slider, - self.dribbler_speed_rpm_label, - self.value_change, - ) - common_widgets.change_button_state(self.stop_and_reset_dribbler, True) - else: - common_widgets.disable_slider(self.dribbler_speed_rpm_slider) - common_widgets.change_button_state(self.stop_and_reset_dribbler, False) - - def disconnect_direct_sliders(self) -> None: - """Disconnect listener for changing values for motor sliders""" - self.x_velocity_slider.valueChanged.disconnect() - self.y_velocity_slider.valueChanged.disconnect() - self.angular_velocity_slider.valueChanged.disconnect() - - def disconnect_dribbler_sliders(self) -> None: - self.dribbler_speed_rpm_slider.valueChanged.disconnect() - - def disconnect_motor_sliders(self) -> None: - self.front_left_motor_slider.valueChanged.disconnect() - self.front_right_motor_slider.valueChanged.disconnect() - self.back_left_motor_slider.valueChanged.disconnect() - self.back_right_motor_slider.valueChanged.disconnect() - - def reset_direct_sliders(self) -> None: - """Reset direct sliders back to 0""" - self.x_velocity_slider.setValue(0) - self.y_velocity_slider.setValue(0) - self.angular_velocity_slider.setValue(0) - - def reset_motor_sliders(self) -> None: - """Reset direct sliders back to 0""" - self.front_left_motor_slider.setValue(0) - self.front_right_motor_slider.setValue(0) - self.back_left_motor_slider.setValue(0) - self.back_right_motor_slider.setValue(0) - - def reset_dribbler_slider(self) -> None: - """Reset the dribbler slider back to 0""" - self.dribbler_speed_rpm_slider.setValue(0) - - def reset_all_sliders(self) -> None: - """Reset all sliders back to 0""" - self.reset_direct_sliders() - self.reset_motor_sliders() - self.reset_dribbler_slider() diff --git a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_REMOTE_15256.py b/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_REMOTE_15256.py deleted file mode 100644 index 233e092716..0000000000 --- a/src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget_REMOTE_15256.py +++ /dev/null @@ -1,432 +0,0 @@ -from pyqtgraph.Qt.QtCore import Qt -from pyqtgraph.Qt.QtWidgets import * -import time -import software.python_bindings as tbots_cpp -from software.thunderscope.common import common_widgets -from proto.import_all_protos import * -from software.thunderscope.proto_unix_io import ProtoUnixIO -from enum import IntEnum - - -class DriveMode(IntEnum): - """Enum for the 2 drive modes (direct velocity and per-motor)""" - - VELOCITY = 0 - MOTOR = 1 - - -class DriveAndDribblerWidget(QWidget): - def __init__(self, proto_unix_io: ProtoUnixIO) -> None: - """Initialize the widget to control the robot's motors - - :param proto_unix_io: the proto_unix_io object - """ - self.input_a = time.time() - self.constants = tbots_cpp.create2021RobotConstants() - QWidget.__init__(self) - - layout = QVBoxLayout() - self.drive_widget = QStackedWidget() - - self.proto_unix_io = proto_unix_io - - # create swappable widget system using stacked widgets - self.direct_velocity_widget = self.setup_direct_velocity( - "Drive - Direct Velocity" - ) - self.per_motor_widget = self.setup_per_motor("Drive - Per Motor") - self.drive_widget.addWidget(self.direct_velocity_widget) - self.drive_widget.addWidget(self.per_motor_widget) - - layout.addWidget(self.setup_drive_switch_radio("Drive Mode Switch")) - layout.addWidget(self.drive_widget) - layout.addWidget(self.setup_dribbler("Dribbler")) - - self.enabled = True - self.per_motor = False - self.setLayout(layout) - self.toggle_control_mode(True) - self.toggle_dribbler_sliders(True) - - def refresh(self) -> None: - """Refresh the widget and send the a MotorControl message with the current values""" - motor_control = MotorControl() - motor_control.dribbler_speed_rpm = int(self.dribbler_speed_rpm_slider.value()) - - if not self.per_motor: - motor_control.ClearField("direct_per_wheel_control") - motor_control.direct_velocity_control.velocity.x_component_meters = ( - self.x_velocity_slider.value() - ) - motor_control.direct_velocity_control.velocity.y_component_meters = ( - self.y_velocity_slider.value() - ) - motor_control.direct_velocity_control.angular_velocity.radians_per_second = self.angular_velocity_slider.value() - else: - motor_control.ClearField("direct_velocity_control") - motor_control.direct_per_wheel_control.front_left_wheel_velocity = ( - self.front_left_motor_slider.value() - ) - motor_control.direct_per_wheel_control.front_right_wheel_velocity = ( - self.front_right_motor_slider.value() - ) - motor_control.direct_per_wheel_control.back_left_wheel_velocity = ( - self.back_left_motor_slider.value() - ) - motor_control.direct_per_wheel_control.back_right_wheel_velocity = ( - self.back_right_motor_slider.value() - ) - - self.proto_unix_io.send_proto(MotorControl, motor_control) - - def value_change(self, value: float) -> str: - """Converts the given float value to a string label - - :param value: float value to be converted - """ - value = float(value) - value_str = "%.2f" % value - return value_str - - def setup_drive_switch_radio(self, title: str) -> QGroupBox: - """Create a radio button widget to switch between per-motor and velocity control modes - - :param title: group box name - """ - group_box = QGroupBox(title) - dbox = QVBoxLayout() - self.connect_options_group = QButtonGroup() - radio_button_names = ["Velocity Control", "Per Motor Control"] - self.connect_options_box, self.connect_options = common_widgets.create_radio( - radio_button_names, self.connect_options_group - ) - self.use_direct_velocity = self.connect_options[DriveMode.VELOCITY] - self.use_per_motor = self.connect_options[DriveMode.MOTOR] - self.use_direct_velocity.clicked.connect(self.switch_to_velocity) - self.use_per_motor.clicked.connect(self.switch_to_motor) - dbox.addWidget(self.connect_options_box) - group_box.setLayout(dbox) - - return group_box - - def switch_to_velocity(self): - self.toggle_control_mode(True) - - def switch_to_motor(self): - self.toggle_control_mode(False) - - def setup_direct_velocity(self, title: str) -> QGroupBox: - """Create a widget to control the direct velocity of the robot's motors - - :param title: the name of the slider - """ - group_box = QGroupBox(title) - dbox = QVBoxLayout() - - ( - x_layout, - self.x_velocity_slider, - self.x_velocity_label, - ) = common_widgets.create_float_slider( - "X (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - ( - y_layout, - self.y_velocity_slider, - self.y_velocity_label, - ) = common_widgets.create_float_slider( - "Y (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - ( - dps_layout, - self.angular_velocity_slider, - self.angular_velocity_label, - ) = common_widgets.create_float_slider( - "θ (rad/s)", - 2, - -self.constants.robot_max_ang_speed_rad_per_s, - self.constants.robot_max_ang_speed_rad_per_s, - 1, - ) - - # add listener functions for sliders to update label with slider value - common_widgets.enable_slider( - self.x_velocity_slider, self.x_velocity_label, self.value_change - ) - common_widgets.enable_slider( - self.y_velocity_slider, self.y_velocity_label, self.value_change - ) - common_widgets.enable_slider( - self.angular_velocity_slider, self.angular_velocity_label, self.value_change - ) - - self.stop_and_reset_direct = QPushButton("Stop and Reset") - self.stop_and_reset_direct.clicked.connect(self.reset_direct_sliders) - - dbox.addLayout(x_layout) - dbox.addLayout(y_layout) - dbox.addLayout(dps_layout) - dbox.addWidget( - self.stop_and_reset_direct, alignment=Qt.AlignmentFlag.AlignCenter - ) - - group_box.setLayout(dbox) - - return group_box - - def setup_per_motor(self, title: str) -> QGroupBox: - """Create a widget to control the rotation rate of each motor - - :param title: the name of the group box - """ - group_box = QGroupBox(title) - dbox = QVBoxLayout() - - ( - fl_layout, - self.front_left_motor_slider, - self.front_left_motor_label, - ) = common_widgets.create_float_slider( - "Front Left Motor (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - ( - fr_layout, - self.front_right_motor_slider, - self.front_right_motor_label, - ) = common_widgets.create_float_slider( - "Front Right Motor (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - ( - bl_layout, - self.back_left_motor_slider, - self.back_left_motor_label, - ) = common_widgets.create_float_slider( - "Back Left Motor (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - ( - br_layout, - self.back_right_motor_slider, - self.back_right_motor_label, - ) = common_widgets.create_float_slider( - "Back Right Motor (m/s)", - 2, - -self.constants.robot_max_speed_m_per_s, - self.constants.robot_max_speed_m_per_s, - 1, - ) - - # Add listener functions for each motor's slider to update labels with slider values - common_widgets.enable_slider( - self.front_left_motor_slider, self.front_left_motor_label, self.value_change - ) - common_widgets.enable_slider( - self.front_right_motor_slider, - self.front_right_motor_label, - self.value_change, - ) - common_widgets.enable_slider( - self.back_left_motor_slider, self.back_left_motor_label, self.value_change - ) - common_widgets.enable_slider( - self.back_right_motor_slider, self.back_right_motor_label, self.value_change - ) - - # Stop and Reset button for per-motor sliders - self.stop_and_reset_per_motor = QPushButton("Stop and Reset") - self.stop_and_reset_per_motor.clicked.connect(self.reset_motor_sliders) - - # Adding layouts to the box - dbox.addLayout(fl_layout) - dbox.addLayout(fr_layout) - dbox.addLayout(bl_layout) - dbox.addLayout(br_layout) - dbox.addWidget( - self.stop_and_reset_per_motor, alignment=Qt.AlignmentFlag.AlignCenter - ) - - group_box.setLayout(dbox) - - return group_box - - def setup_dribbler(self, title: str) -> QGroupBox: - """Create a widget to control the dribbler RPM - - :param title: the name of the slider - """ - group_box = QGroupBox(title) - dbox = QVBoxLayout() - - ( - dribbler_layout, - self.dribbler_speed_rpm_slider, - self.dribbler_speed_rpm_label, - ) = common_widgets.create_float_slider( - "RPM", - 1, - self.constants.indefinite_dribbler_speed_rpm, - -self.constants.indefinite_dribbler_speed_rpm, - 1, - ) - - # add listener function to update label with slider value - common_widgets.enable_slider( - self.dribbler_speed_rpm_slider, - self.dribbler_speed_rpm_label, - self.value_change, - ) - - self.stop_and_reset_dribbler = QPushButton("Stop and Reset") - self.stop_and_reset_dribbler.clicked.connect(self.reset_dribbler_slider) - - dbox.addLayout(dribbler_layout) - dbox.addWidget( - self.stop_and_reset_dribbler, alignment=Qt.AlignmentFlag.AlignCenter - ) - group_box.setLayout(dbox) - - return group_box - - def toggle_control_mode(self, use_direct: bool) -> None: - """Switches between 'Direct Velocity' and 'Per Motor' control modes. - - :param use_direct: True to enable Direct Velocity mode, False to enable Per Motor mode. - """ - # reset sliders - self.reset_motor_sliders() - self.reset_direct_sliders() - self.disconnect_direct_sliders() - self.disconnect_motor_sliders() - - if use_direct: - # Show the direct velocity widget - self.per_motor = False - self.drive_widget.setCurrentWidget(self.direct_velocity_widget) - - # Enable direct sliders and disable motor sliders - common_widgets.enable_slider( - self.x_velocity_slider, self.x_velocity_label, self.value_change - ) - common_widgets.enable_slider( - self.y_velocity_slider, self.y_velocity_label, self.value_change - ) - common_widgets.enable_slider( - self.angular_velocity_slider, - self.angular_velocity_label, - self.value_change, - ) - - common_widgets.disable_slider(self.front_left_motor_slider) - common_widgets.disable_slider(self.front_right_motor_slider) - common_widgets.disable_slider(self.back_left_motor_slider) - common_widgets.disable_slider(self.back_right_motor_slider) - - common_widgets.change_button_state(self.stop_and_reset_direct, True) - common_widgets.change_button_state(self.stop_and_reset_per_motor, False) - - else: - self.per_motor = True - # Show the per motor widget - self.drive_widget.setCurrentWidget(self.per_motor_widget) - - # Enable motor sliders and disable direct sliders - common_widgets.enable_slider( - self.front_left_motor_slider, - self.front_left_motor_label, - self.value_change, - ) - common_widgets.enable_slider( - self.front_right_motor_slider, - self.front_right_motor_label, - self.value_change, - ) - common_widgets.enable_slider( - self.back_left_motor_slider, - self.back_left_motor_label, - self.value_change, - ) - common_widgets.enable_slider( - self.back_right_motor_slider, - self.back_right_motor_label, - self.value_change, - ) - - common_widgets.disable_slider(self.x_velocity_slider) - common_widgets.disable_slider(self.y_velocity_slider) - common_widgets.disable_slider(self.angular_velocity_slider) - - common_widgets.change_button_state(self.stop_and_reset_per_motor, True) - common_widgets.change_button_state(self.stop_and_reset_direct, False) - - def toggle_dribbler_sliders(self, enable: bool) -> None: - """Enables or disables dribbler sliders. - - :param enable: True to enable, False to disable - """ - if enable: - common_widgets.enable_slider( - self.dribbler_speed_rpm_slider, - self.dribbler_speed_rpm_label, - self.value_change, - ) - common_widgets.change_button_state(self.stop_and_reset_dribbler, True) - else: - common_widgets.disable_slider(self.dribbler_speed_rpm_slider) - common_widgets.change_button_state(self.stop_and_reset_dribbler, False) - - def disconnect_direct_sliders(self) -> None: - """Disconnect listener for changing values for motor sliders""" - self.x_velocity_slider.valueChanged.disconnect() - self.y_velocity_slider.valueChanged.disconnect() - self.angular_velocity_slider.valueChanged.disconnect() - - def disconnect_dribbler_sliders(self) -> None: - self.dribbler_speed_rpm_slider.valueChanged.disconnect() - - def disconnect_motor_sliders(self) -> None: - self.front_left_motor_slider.valueChanged.disconnect() - self.front_right_motor_slider.valueChanged.disconnect() - self.back_left_motor_slider.valueChanged.disconnect() - self.back_right_motor_slider.valueChanged.disconnect() - - def reset_direct_sliders(self) -> None: - """Reset direct sliders back to 0""" - self.x_velocity_slider.setValue(0) - self.y_velocity_slider.setValue(0) - self.angular_velocity_slider.setValue(0) - - def reset_motor_sliders(self) -> None: - """Reset direct sliders back to 0""" - self.front_left_motor_slider.setValue(0) - self.front_right_motor_slider.setValue(0) - self.back_left_motor_slider.setValue(0) - self.back_right_motor_slider.setValue(0) - - def reset_dribbler_slider(self) -> None: - """Reset the dribbler slider back to 0""" - self.dribbler_speed_rpm_slider.setValue(0) - - def reset_all_sliders(self) -> None: - """Reset all sliders back to 0""" - self.reset_direct_sliders() - self.reset_motor_sliders() - self.reset_dribbler_slider() From 8483bc1b7ee28760884f5dc353c330505f5738c7 Mon Sep 17 00:00:00 2001 From: muk Date: Mon, 25 Nov 2024 16:34:29 -0800 Subject: [PATCH 08/17] Implemented voltage indicator color changer, fixed getPercentage, tested with robot. --- .../thunderscope/common/common_widgets.py | 50 ++++++++++++------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/src/software/thunderscope/common/common_widgets.py b/src/software/thunderscope/common/common_widgets.py index 87deecdc17..bcd216ddfb 100644 --- a/src/software/thunderscope/common/common_widgets.py +++ b/src/software/thunderscope/common/common_widgets.py @@ -2,6 +2,7 @@ from pyqtgraph.Qt.QtWidgets import * from pyqtgraph.Qt.QtCore import * from software.py_constants import * +import math class FloatSlider(QSlider): @@ -147,23 +148,38 @@ def setValue(self, value: float) -> None: """ super(ColorProgressBar, self).setValue(int(value * self.decimals)) - # clamp percent to make sure it's between 0% and 100% - percent = self.getPercentage() + def sigmoid_interpolate(val, x1, y1, x2, y2): + """ + Interpolates a value along a sigmoid curve. + + :param val: Value to interpolate + :param x1: Lower bound + :param y1: Value at lower bound + :param x2: Upper bound + :param y2: Value at upper bound + :return: The sigmoid-interpolated value + """ + xc = (x1 + x2) / 2 + k = 10 / (x2 - x1) + return y1 + (y2 - y1) / (1 + math.exp(-k * (val - xc))) - if percent < 0.5: - super(ColorProgressBar, self).setStyleSheet( - "QProgressBar::chunk" - "{" - f"background: rgb(255, {255 * (2 * percent)}, 0)" - "}" - ) + percent = self.getPercentage() + if 0 < percent < 0.5: + r = 200 + g = sigmoid_interpolate(percent, 0, 0, 0.5, 200) + elif percent < 0: + r = 200 + g = 0 else: - super(ColorProgressBar, self).setStyleSheet( - "QProgressBar::chunk" - "{" - f"background: rgb({255 * 2 * (1 - percent)}, 255, 0)" - "}" - ) + r = sigmoid_interpolate(percent, 0.5, 200, 1, 0) + g = 200 + + super(ColorProgressBar, self).setStyleSheet( + "QProgressBar::chunk" + "{" + f"background: rgb({r}, {g}, 0)" + "}" + ) def getPercentage(self): """Gets the current percentage between 0 and 1 from the current value @@ -173,9 +189,7 @@ def getPercentage(self): 1, max( 0, - int( - (self.value() - self.minimum()) / (self.maximum() - self.minimum()) - ), + (self.value() - self.minimum()) / (self.maximum() - self.minimum()), ), ) From fd3abffb2a8a00692636cc7ba8986fc58159a71b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 30 Nov 2024 23:56:08 +0000 Subject: [PATCH 09/17] [pre-commit.ci lite] apply automatic fixes --- src/software/thunderscope/common/common_widgets.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/software/thunderscope/common/common_widgets.py b/src/software/thunderscope/common/common_widgets.py index bcd216ddfb..eef80be817 100644 --- a/src/software/thunderscope/common/common_widgets.py +++ b/src/software/thunderscope/common/common_widgets.py @@ -149,8 +149,7 @@ def setValue(self, value: float) -> None: super(ColorProgressBar, self).setValue(int(value * self.decimals)) def sigmoid_interpolate(val, x1, y1, x2, y2): - """ - Interpolates a value along a sigmoid curve. + """Interpolates a value along a sigmoid curve. :param val: Value to interpolate :param x1: Lower bound @@ -175,10 +174,7 @@ def sigmoid_interpolate(val, x1, y1, x2, y2): g = 200 super(ColorProgressBar, self).setStyleSheet( - "QProgressBar::chunk" - "{" - f"background: rgb({r}, {g}, 0)" - "}" + "QProgressBar::chunk" "{" f"background: rgb({r}, {g}, 0)" "}" ) def getPercentage(self): From 2be456ae5f2501157a86fb701fb0b8b4ca786e9a Mon Sep 17 00:00:00 2001 From: muk Date: Sat, 11 Jan 2025 15:35:41 -0800 Subject: [PATCH 10/17] Switched to using util color gradient. --- .../thunderscope/common/common_widgets.py | 35 ++++++------------- 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/src/software/thunderscope/common/common_widgets.py b/src/software/thunderscope/common/common_widgets.py index eef80be817..fe5edb84aa 100644 --- a/src/software/thunderscope/common/common_widgets.py +++ b/src/software/thunderscope/common/common_widgets.py @@ -2,6 +2,7 @@ from pyqtgraph.Qt.QtWidgets import * from pyqtgraph.Qt.QtCore import * from software.py_constants import * +from software.util import color_from_gradient import math @@ -148,33 +149,19 @@ def setValue(self, value: float) -> None: """ super(ColorProgressBar, self).setValue(int(value * self.decimals)) - def sigmoid_interpolate(val, x1, y1, x2, y2): - """Interpolates a value along a sigmoid curve. - - :param val: Value to interpolate - :param x1: Lower bound - :param y1: Value at lower bound - :param x2: Upper bound - :param y2: Value at upper bound - :return: The sigmoid-interpolated value - """ - xc = (x1 + x2) / 2 - k = 10 / (x2 - x1) - return y1 + (y2 - y1) / (1 + math.exp(-k * (val - xc))) - percent = self.getPercentage() - if 0 < percent < 0.5: - r = 200 - g = sigmoid_interpolate(percent, 0, 0, 0.5, 200) - elif percent < 0: - r = 200 - g = 0 - else: - r = sigmoid_interpolate(percent, 0.5, 200, 1, 0) - g = 200 + color = color_from_gradient( + percent, + [0, 0.5, 1.0], + [255, 255, 0], + [0, 255, 255], + [0, 0, 0], + [255, 255, 255] + ) + # Extract color into CSS form. super(ColorProgressBar, self).setStyleSheet( - "QProgressBar::chunk" "{" f"background: rgb({r}, {g}, 0)" "}" + "QProgressBar::chunk" "{" f"background: rgb({color.red}, {color.green}, {color.blue})" "}" ) def getPercentage(self): From 9056bbdd248a73be1300b0f5c5dcdfb243a68ebf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 11 Jan 2025 23:46:27 +0000 Subject: [PATCH 11/17] [pre-commit.ci lite] apply automatic fixes --- src/software/thunderscope/common/common_widgets.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/software/thunderscope/common/common_widgets.py b/src/software/thunderscope/common/common_widgets.py index fe5edb84aa..5edac865b9 100644 --- a/src/software/thunderscope/common/common_widgets.py +++ b/src/software/thunderscope/common/common_widgets.py @@ -3,7 +3,6 @@ from pyqtgraph.Qt.QtCore import * from software.py_constants import * from software.util import color_from_gradient -import math class FloatSlider(QSlider): @@ -156,12 +155,15 @@ def setValue(self, value: float) -> None: [255, 255, 0], [0, 255, 255], [0, 0, 0], - [255, 255, 255] + [255, 255, 255], ) # Extract color into CSS form. super(ColorProgressBar, self).setStyleSheet( - "QProgressBar::chunk" "{" f"background: rgb({color.red}, {color.green}, {color.blue})" "}" + "QProgressBar::chunk" + "{" + f"background: rgb({color.red}, {color.green}, {color.blue})" + "}" ) def getPercentage(self): From bd78f7ea9ea66bb0d4bbbca9a113b148eb8ccf54 Mon Sep 17 00:00:00 2001 From: muk Date: Sat, 11 Jan 2025 16:01:11 -0800 Subject: [PATCH 12/17] Need testing --- src/software/thunderscope/common/common_widgets.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/software/thunderscope/common/common_widgets.py b/src/software/thunderscope/common/common_widgets.py index 5edac865b9..8cb0d7600a 100644 --- a/src/software/thunderscope/common/common_widgets.py +++ b/src/software/thunderscope/common/common_widgets.py @@ -2,7 +2,7 @@ from pyqtgraph.Qt.QtWidgets import * from pyqtgraph.Qt.QtCore import * from software.py_constants import * -from software.util import color_from_gradient +from software.thunderscope.util import color_from_gradient class FloatSlider(QSlider): @@ -155,15 +155,12 @@ def setValue(self, value: float) -> None: [255, 255, 0], [0, 255, 255], [0, 0, 0], - [255, 255, 255], + [255, 255, 255] ) # Extract color into CSS form. super(ColorProgressBar, self).setStyleSheet( - "QProgressBar::chunk" - "{" - f"background: rgb({color.red}, {color.green}, {color.blue})" - "}" + "QProgressBar::chunk" "{" f"background: rgb({color.red}, {color.green}, {color.blue})" "}" ) def getPercentage(self): From 9b7b91bcb48c3d83384b369ae114d616e1d759b2 Mon Sep 17 00:00:00 2001 From: muk Date: Sat, 25 Jan 2025 13:29:33 -0800 Subject: [PATCH 13/17] voltage colors tested and good to go --- src/software/thunderscope/common/common_widgets.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/software/thunderscope/common/common_widgets.py b/src/software/thunderscope/common/common_widgets.py index 8cb0d7600a..2be135fc2c 100644 --- a/src/software/thunderscope/common/common_widgets.py +++ b/src/software/thunderscope/common/common_widgets.py @@ -151,16 +151,16 @@ def setValue(self, value: float) -> None: percent = self.getPercentage() color = color_from_gradient( percent, - [0, 0.5, 1.0], - [255, 255, 0], - [0, 255, 255], + [0, 0.5, 1], + [255, 200, 0], + [0, 170, 180], [0, 0, 0], [255, 255, 255] ) # Extract color into CSS form. super(ColorProgressBar, self).setStyleSheet( - "QProgressBar::chunk" "{" f"background: rgb({color.red}, {color.green}, {color.blue})" "}" + "QProgressBar::chunk" "{" f"background: rgb({color.red()}, {color.green()}, {color.blue()})" "}" ) def getPercentage(self): From b596e6af6ff41dc984e8311e744377327408f8d7 Mon Sep 17 00:00:00 2001 From: muk Date: Thu, 20 Mar 2025 18:11:43 -0700 Subject: [PATCH 14/17] added util.py to BUILD --- src/software/thunderscope/common/BUILD | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/software/thunderscope/common/BUILD b/src/software/thunderscope/common/BUILD index 3f7077fa67..1c928a862a 100644 --- a/src/software/thunderscope/common/BUILD +++ b/src/software/thunderscope/common/BUILD @@ -41,3 +41,11 @@ py_library( ":frametime_counter", ], ) + +py_library( + name = "util", + srcs = ["util.py"], + deps = [ + ":util", + ], +) \ No newline at end of file From 5bae8ee414846c7c1761f8fc0b1a37b3a0ad7c41 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 10:51:27 +0000 Subject: [PATCH 15/17] [pre-commit.ci lite] apply automatic fixes --- src/software/thunderscope/common/BUILD | 1 - src/software/thunderscope/common/common_widgets.py | 7 +++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/software/thunderscope/common/BUILD b/src/software/thunderscope/common/BUILD index 0cec4bc8c3..2d83064f74 100644 --- a/src/software/thunderscope/common/BUILD +++ b/src/software/thunderscope/common/BUILD @@ -60,4 +60,3 @@ py_library( requirement("pyqt-toast-notification"), ], ) - diff --git a/src/software/thunderscope/common/common_widgets.py b/src/software/thunderscope/common/common_widgets.py index 2be135fc2c..4b5516a4f5 100644 --- a/src/software/thunderscope/common/common_widgets.py +++ b/src/software/thunderscope/common/common_widgets.py @@ -155,12 +155,15 @@ def setValue(self, value: float) -> None: [255, 200, 0], [0, 170, 180], [0, 0, 0], - [255, 255, 255] + [255, 255, 255], ) # Extract color into CSS form. super(ColorProgressBar, self).setStyleSheet( - "QProgressBar::chunk" "{" f"background: rgb({color.red()}, {color.green()}, {color.blue()})" "}" + "QProgressBar::chunk" + "{" + f"background: rgb({color.red()}, {color.green()}, {color.blue()})" + "}" ) def getPercentage(self): From 597e3d5f1b8541130dc80fbd34a1abb1f8f00b89 Mon Sep 17 00:00:00 2001 From: Muk Chunpongtong <121708205+Muxite@users.noreply.github.com> Date: Sun, 6 Apr 2025 00:12:54 -0700 Subject: [PATCH 16/17] Update BUILD --- src/software/thunderscope/common/BUILD | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/software/thunderscope/common/BUILD b/src/software/thunderscope/common/BUILD index 2d83064f74..cdfe02e416 100644 --- a/src/software/thunderscope/common/BUILD +++ b/src/software/thunderscope/common/BUILD @@ -48,9 +48,6 @@ py_library( py_library( name = "util", srcs = ["util.py"], - deps = [ - ":util", - ], ) py_library( From fedf3c31bf66a714393b8317b7ba626b81f02634 Mon Sep 17 00:00:00 2001 From: Muk Chunpongtong <121708205+Muxite@users.noreply.github.com> Date: Sun, 13 Apr 2025 23:49:22 -0700 Subject: [PATCH 17/17] Update BUILD --- src/software/thunderscope/common/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/software/thunderscope/common/BUILD b/src/software/thunderscope/common/BUILD index cdfe02e416..561886b9ad 100644 --- a/src/software/thunderscope/common/BUILD +++ b/src/software/thunderscope/common/BUILD @@ -47,7 +47,7 @@ py_library( py_library( name = "util", - srcs = ["util.py"], + srcs = ["//software/thunderscope:util.py"], ) py_library(