Skip to content

Commit 28ffa52

Browse files
Added per-motor control to thunderscope diagnostics. (new) (UBC-Thunderbots#3423)
* Merge complete, visually functioning, physical test needed. * tested good to go * [pre-commit.ci lite] apply automatic fixes * moved clearfield to run only when switching mode --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent 9975f65 commit 28ffa52

1 file changed

Lines changed: 193 additions & 14 deletions

File tree

src/software/thunderscope/robot_diagnostics/drive_and_dribbler_widget.py

Lines changed: 193 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
from pyqtgraph.Qt.QtCore import Qt
22
from pyqtgraph.Qt.QtWidgets import *
33
from proto.import_all_protos import *
4-
4+
from enum import IntEnum
55
import software.python_bindings as tbots_cpp
66

77
from software.thunderscope.proto_unix_io import ProtoUnixIO
88
from software.thunderscope.common import common_widgets
99

1010

11+
class ControlMode(IntEnum):
12+
"""Enum for the 2 drive modes (direct velocity and per-motor)"""
13+
14+
VELOCITY = 0
15+
MOTOR = 1
16+
17+
1118
class DriveAndDribblerWidget(QWidget):
1219
"""This widget provides an interface for controlling our robots'
1320
drive and dribbler functionalities. It has sliders for manipulating
@@ -28,11 +35,15 @@ def __init__(self, proto_unix_io: ProtoUnixIO) -> None:
2835
self.constants = tbots_cpp.create2021RobotConstants()
2936

3037
self.enabled = True
38+
self.control_mode = ControlMode.VELOCITY
3139

3240
layout = QVBoxLayout()
41+
layout.addWidget(self.__setup_drive_switch_radio())
3342
layout.addWidget(self.__setup_direct_velocity_widgets())
43+
layout.addWidget(self.__setup_per_motor_widgets())
3444
layout.addWidget(self.__setup_dribbler_widgets())
3545
self.setLayout(layout)
46+
self.use_direct_velocity.click()
3647

3748
def enable(self) -> None:
3849
"""Enable all sliders and buttons in the DriveAndDribblerWidget"""
@@ -44,6 +55,10 @@ def enable(self) -> None:
4455
common_widgets.enable_slider(self.x_velocity_slider)
4556
common_widgets.enable_slider(self.y_velocity_slider)
4657
common_widgets.enable_slider(self.angular_velocity_slider)
58+
common_widgets.enable_slider(self.front_left_motor_slider)
59+
common_widgets.enable_slider(self.front_right_motor_slider)
60+
common_widgets.enable_slider(self.back_left_motor_slider)
61+
common_widgets.enable_slider(self.back_left_motor_slider)
4762
common_widgets.enable_slider(self.dribbler_speed_rpm_slider)
4863
common_widgets.enable_button(self.stop_and_reset_dribbler)
4964
common_widgets.enable_button(self.stop_and_reset_direct)
@@ -59,6 +74,10 @@ def disable(self) -> None:
5974
common_widgets.disable_slider(self.x_velocity_slider)
6075
common_widgets.disable_slider(self.y_velocity_slider)
6176
common_widgets.disable_slider(self.angular_velocity_slider)
77+
common_widgets.disable_slider(self.front_left_motor_slider)
78+
common_widgets.disable_slider(self.front_right_motor_slider)
79+
common_widgets.disable_slider(self.back_left_motor_slider)
80+
common_widgets.disable_slider(self.back_left_motor_slider)
6281
common_widgets.disable_slider(self.dribbler_speed_rpm_slider)
6382
common_widgets.disable_button(self.stop_and_reset_dribbler)
6483
common_widgets.disable_button(self.stop_and_reset_direct)
@@ -77,23 +96,46 @@ def override_slider_values(self, motor_control: MotorControl) -> None:
7796
self.angular_velocity_slider.setValue(
7897
motor_control.direct_velocity_control.angular_velocity.radians_per_second
7998
)
99+
self.front_left_motor_slider.setValue(
100+
motor_control.direct_velocity_control.front_left_wheel_velocity
101+
)
102+
self.front_right_motor_slider.setValue(
103+
motor_control.direct_velocity_control.front_right_wheel_velocity
104+
)
105+
self.back_left_motor_slider.setValue(
106+
motor_control.direct_velocity_control.back_left_wheel_velocity
107+
)
108+
self.back_right_motor_slider.setValue(
109+
motor_control.direct_velocity_control.back_right_wheel_velocity
110+
)
111+
80112
self.dribbler_speed_rpm_slider.setValue(motor_control.dribbler_speed_rpm)
81113

82114
def refresh(self) -> None:
83-
"""Send out a MotorControl proto with the currently set direct velocity and
84-
dribbler speed values
85-
"""
115+
"""Refresh the widget and send the MotorControl message with the current values depending on the ControlMode"""
86116
motor_control = MotorControl()
87-
motor_control.direct_velocity_control.velocity.x_component_meters = (
88-
self.x_velocity_slider.value()
89-
)
90-
motor_control.direct_velocity_control.velocity.y_component_meters = (
91-
self.y_velocity_slider.value()
92-
)
93-
motor_control.direct_velocity_control.angular_velocity.radians_per_second = (
94-
self.angular_velocity_slider.value()
95-
)
96117
motor_control.dribbler_speed_rpm = int(self.dribbler_speed_rpm_slider.value())
118+
if self.control_mode == ControlMode.VELOCITY:
119+
motor_control.direct_velocity_control.velocity.x_component_meters = (
120+
self.x_velocity_slider.value()
121+
)
122+
motor_control.direct_velocity_control.velocity.y_component_meters = (
123+
self.y_velocity_slider.value()
124+
)
125+
motor_control.direct_velocity_control.angular_velocity.radians_per_second = self.angular_velocity_slider.value()
126+
else:
127+
motor_control.direct_per_wheel_control.front_left_wheel_velocity = (
128+
self.front_left_motor_slider.value()
129+
)
130+
motor_control.direct_per_wheel_control.front_right_wheel_velocity = (
131+
self.front_right_motor_slider.value()
132+
)
133+
motor_control.direct_per_wheel_control.back_left_wheel_velocity = (
134+
self.back_left_motor_slider.value()
135+
)
136+
motor_control.direct_per_wheel_control.back_right_wheel_velocity = (
137+
self.back_right_motor_slider.value()
138+
)
97139

98140
self.proto_unix_io.send_proto(MotorControl, motor_control)
99141

@@ -158,7 +200,113 @@ def __setup_direct_velocity_widgets(self) -> QGroupBox:
158200
self.stop_and_reset_direct, alignment=Qt.AlignmentFlag.AlignCenter
159201
)
160202

161-
group_box = QGroupBox("Drive")
203+
group_box = QGroupBox("Velocity Control")
204+
group_box.setLayout(vbox)
205+
self.direct_velocity_widget = group_box
206+
207+
return group_box
208+
209+
def __setup_per_motor_widgets(self) -> QGroupBox:
210+
""":returns: a QGroupBox containing sliders and controls for controlling individual
211+
speed of the robot's motors.
212+
"""
213+
(
214+
fl_layout,
215+
self.front_left_motor_slider,
216+
self.front_left_motor_label,
217+
) = common_widgets.create_float_slider(
218+
"Front Left Motor (m/s)",
219+
2,
220+
-self.constants.robot_max_speed_m_per_s,
221+
self.constants.robot_max_speed_m_per_s,
222+
1,
223+
)
224+
(
225+
fr_layout,
226+
self.front_right_motor_slider,
227+
self.front_right_motor_label,
228+
) = common_widgets.create_float_slider(
229+
"Front Right Motor (m/s)",
230+
2,
231+
-self.constants.robot_max_speed_m_per_s,
232+
self.constants.robot_max_speed_m_per_s,
233+
1,
234+
)
235+
(
236+
bl_layout,
237+
self.back_left_motor_slider,
238+
self.back_left_motor_label,
239+
) = common_widgets.create_float_slider(
240+
"Back Left Motor (m/s)",
241+
2,
242+
-self.constants.robot_max_speed_m_per_s,
243+
self.constants.robot_max_speed_m_per_s,
244+
1,
245+
)
246+
(
247+
br_layout,
248+
self.back_right_motor_slider,
249+
self.back_right_motor_label,
250+
) = common_widgets.create_float_slider(
251+
"Back Right Motor (m/s)",
252+
2,
253+
-self.constants.robot_max_speed_m_per_s,
254+
self.constants.robot_max_speed_m_per_s,
255+
1,
256+
)
257+
258+
self.front_left_motor_slider.floatValueChanged.connect(
259+
lambda new_value: self.front_left_motor_label.setText("%.2f" % new_value)
260+
)
261+
self.front_right_motor_slider.floatValueChanged.connect(
262+
lambda new_value: self.front_right_motor_label.setText("%.2f" % new_value)
263+
)
264+
self.back_left_motor_slider.floatValueChanged.connect(
265+
lambda new_value: self.back_left_motor_label.setText("%.2f" % new_value)
266+
)
267+
self.back_right_motor_slider.floatValueChanged.connect(
268+
lambda new_value: self.back_right_motor_label.setText("%.2f" % new_value)
269+
)
270+
271+
self.stop_and_reset_per_motor = QPushButton("Stop and Reset")
272+
self.stop_and_reset_per_motor.clicked.connect(self.__reset_motor_sliders)
273+
274+
vbox = QVBoxLayout()
275+
vbox.addLayout(fl_layout)
276+
vbox.addLayout(fr_layout)
277+
vbox.addLayout(bl_layout)
278+
vbox.addLayout(br_layout)
279+
vbox.addWidget(
280+
self.stop_and_reset_per_motor, alignment=Qt.AlignmentFlag.AlignCenter
281+
)
282+
283+
group_box = QGroupBox("Per Motor Control")
284+
group_box.setLayout(vbox)
285+
self.per_motor_widget = group_box
286+
287+
return group_box
288+
289+
def __setup_drive_switch_radio(self) -> QGroupBox:
290+
"""Create a radio button widget to switch between per-motor and velocity drive modes
291+
292+
:returns: The group box of the radio button switch.
293+
"""
294+
group_box = QGroupBox()
295+
vbox = QVBoxLayout()
296+
self.connect_options_group = QButtonGroup()
297+
radio_button_names = ["Velocity Control", "Per Motor Control"]
298+
self.connect_options_box, self.connect_options = common_widgets.create_radio(
299+
radio_button_names, self.connect_options_group
300+
)
301+
self.use_direct_velocity = self.connect_options[ControlMode.VELOCITY]
302+
self.use_per_motor = self.connect_options[ControlMode.MOTOR]
303+
self.use_direct_velocity.clicked.connect(
304+
lambda: self.toggle_control_mode(ControlMode.VELOCITY)
305+
)
306+
self.use_per_motor.clicked.connect(
307+
lambda: self.toggle_control_mode(ControlMode.MOTOR)
308+
)
309+
vbox.addWidget(self.connect_options_box)
162310
group_box.setLayout(vbox)
163311

164312
return group_box
@@ -199,12 +347,42 @@ def __setup_dribbler_widgets(self) -> QGroupBox:
199347

200348
return group_box
201349

350+
def toggle_control_mode(self, use_control_mode: IntEnum) -> None:
351+
"""Switches between 'Direct Velocity' and 'Per Motor' drive modes.
352+
353+
:param use_control_mode: ControlMode.VELOCITY or ControlMode.MOTOR, switch to that mode.
354+
"""
355+
self.control_mode = use_control_mode
356+
# reset sliders
357+
self.__reset_motor_sliders()
358+
self.__reset_direct_sliders()
359+
360+
motor_control = MotorControl()
361+
if use_control_mode == ControlMode.VELOCITY:
362+
# Show the direct velocity widget
363+
motor_control.ClearField("direct_per_wheel_control")
364+
self.direct_velocity_widget.setVisible(True)
365+
self.per_motor_widget.setVisible(False)
366+
else:
367+
# Show the per motor widget
368+
motor_control.ClearField("direct_velocity_control")
369+
self.direct_velocity_widget.setVisible(False)
370+
self.per_motor_widget.setVisible(True)
371+
self.proto_unix_io.send_proto(MotorControl, motor_control)
372+
202373
def __reset_direct_sliders(self) -> None:
203374
"""Reset the direct velocity sliders back to 0"""
204375
self.x_velocity_slider.setValue(0)
205376
self.y_velocity_slider.setValue(0)
206377
self.angular_velocity_slider.setValue(0)
207378

379+
def __reset_motor_sliders(self) -> None:
380+
"""Reset direct sliders back to 0"""
381+
self.front_left_motor_slider.setValue(0)
382+
self.front_right_motor_slider.setValue(0)
383+
self.back_left_motor_slider.setValue(0)
384+
self.back_right_motor_slider.setValue(0)
385+
208386
def __reset_dribbler_slider(self) -> None:
209387
"""Reset the dribbler speed slider back to 0"""
210388
self.dribbler_speed_rpm_slider.setValue(0)
@@ -213,3 +391,4 @@ def __reset_all_sliders(self) -> None:
213391
"""Reset all sliders back to 0"""
214392
self.__reset_direct_sliders()
215393
self.__reset_dribbler_slider()
394+
self.__reset_direct_sliders()

0 commit comments

Comments
 (0)