11from pyqtgraph .Qt .QtCore import Qt
22from pyqtgraph .Qt .QtWidgets import *
33from proto .import_all_protos import *
4-
4+ from enum import IntEnum
55import software .python_bindings as tbots_cpp
66
77from software .thunderscope .proto_unix_io import ProtoUnixIO
88from 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+
1118class 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