diff --git a/pycrocart/ControlTab.py b/pycrocart/ControlTab.py index 8d012bb007e3c040577b599a09a3d7c3cf916348..0c6bb3e6b68659eef0676bda97adfbd2e132b63a 100644 --- a/pycrocart/ControlTab.py +++ b/pycrocart/ControlTab.py @@ -41,7 +41,7 @@ class ControlTab(QWidget): self.plotting_window = PlottingWindow() self.setpoint_menu = SetpointMenu(setpoint_handler, joystick) - self.logging_menu = LoggingSelectionMenu() + self.logging_menu = LoggingSelectionMenu(self.external_press_pause) win2 = QWidget() win2.setLayout(left_side_vertical_layout) @@ -91,6 +91,11 @@ class ControlTab(QWidget): except queue.Empty: # done emptying logging queue not_empty = False + def external_press_pause(self): + """ Made so the logging selection menu can press the pause button + programatically whenever configurations change. """ + self.play_or_pause_logging_button.click() + def play_pause_button_callback(self): """ This function handles getting the logging to stop or to continue graphing. Called when button is clicked. """ diff --git a/pycrocart/CrazyflieProtoConnection.py b/pycrocart/CrazyflieProtoConnection.py index 9b59948bc5016ca8171b1c1e70bab90ec7b6be46..b2a0ed79e8ec13e37bcb832206f0b8122dbba8ab 100644 --- a/pycrocart/CrazyflieProtoConnection.py +++ b/pycrocart/CrazyflieProtoConnection.py @@ -207,6 +207,7 @@ class CrazyflieProtoConnection: def disconnect(self): """ Disconnect from crazyflie. """ + print("Disconnect quad") if self.is_connected: self.scf.close_link() self.scf = None diff --git a/pycrocart/GamepadWizardTab.py b/pycrocart/GamepadWizardTab.py index 3e16ea5987287ce44f213ec5ecb5a073e3dc0efd..c4e981052c29a94552a5d56554f9319839117f36 100644 --- a/pycrocart/GamepadWizardTab.py +++ b/pycrocart/GamepadWizardTab.py @@ -369,6 +369,7 @@ class InputConfigDialogue(QtWidgets.QWidget, inputconfig_widget_class): QMessageBox.critical(self, caption, message) def _load_config_from_file(self): + print("Loading config") loaded_map = ConfigManager().get_config( self.profileCombo.currentText()) if loaded_map: diff --git a/pycrocart/LoggingSelectionMenu.py b/pycrocart/LoggingSelectionMenu.py index 4d19a48acca40b8ae5fa738485e35ecb0b2deb28..01e05eb13db1c51d1af016724c153d839623b3f9 100644 --- a/pycrocart/LoggingSelectionMenu.py +++ b/pycrocart/LoggingSelectionMenu.py @@ -1,4 +1,5 @@ -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QComboBox +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QComboBox, QLabel +from PyQt5.QtCore import Qt class LoggingSelectionMenu(QWidget): @@ -7,11 +8,14 @@ class LoggingSelectionMenu(QWidget): valid logging signals through a series of combo boxes. """ - def __init__(self): + def __init__(self, config_changed_callback): super().__init__() layout = QVBoxLayout() self.setLayout(layout) + self.config_changed_callback = config_changed_callback # whenever + # the config changes, send a signal to the controls tab to press the + # pause button self.available_logging_variables = [] @@ -19,11 +23,20 @@ class LoggingSelectionMenu(QWidget): QComboBox()] # Default logging variables to Logging Variable 1,2,3,4,5 + colors = ['red', 'orange', 'purple', 'green', 'blue'] + for i in range(0, len(self.cbs)): lst = ["Logging Variable " + str(i+1)] lst.extend(self.available_logging_variables) self.cbs[i].addItems(lst) - layout.addWidget(self.cbs[i], i+1) + layout.addWidget(self.cbs[i], 1+2*i) + + color_label = QLabel("Color Label") + color_label.setStyleSheet("QLabel {color: " + colors[i] + + ";background : " + colors[i] + "}") + color_label.setMaximumHeight(5) + layout.addWidget(color_label, 1+2*i+1, alignment=Qt.AlignHCenter) + # add callback for when new selection happens self.cbs[i].currentIndexChanged.connect( self.selection_change_callback) @@ -34,7 +47,7 @@ class LoggingSelectionMenu(QWidget): """ Used for determining which signal is which in plotting window. """ for i in range(0, len(self.cbs)): if signal_name == self.cbs[i].currentText(): - return i+1 + return i def update_available_logging_variables(self, variables_list: list): """ This function is called by the logging config tab upon refreshing @@ -44,6 +57,7 @@ class LoggingSelectionMenu(QWidget): for variable in variables_list: for sub_variable in variable: self.available_logging_variables.append(sub_variable) + self.config_changed_callback() self.selection_change_callback() # todo handle variables that used to be in the list and no longer are @@ -58,7 +72,6 @@ class LoggingSelectionMenu(QWidget): self.cbs[2].currentText(), self.cbs[3].currentText(), self.cbs[4].currentText() ] - modified_selection_signals = [] # find any signals that are different from before. diff --git a/pycrocart/PlottingWindow.py b/pycrocart/PlottingWindow.py index 520c40c8a16fe44ca56c3a0b1070e458526b8e59..1ec44beb7a7c26e862acd3027792dea345bb379c 100644 --- a/pycrocart/PlottingWindow.py +++ b/pycrocart/PlottingWindow.py @@ -37,9 +37,10 @@ class PlottingWindow(pg.PlotWidget): self.plot_data_ys = np.zeros((1000, num_axes)) self.plot_data_xs = np.zeros((1000, num_axes)) + colors = ['red', 'orange', 'purple', 'green', 'blue'] self.plot_curves = \ [self.plot(self.plot_data_xs[:, i], self.plot_data_ys[:, i], - pen=pg.mkPen(pg.intColor(i), width=2)) + pen=pg.mkPen(colors[i], width=2)) for i in range(num_axes)] def update_plot(self, input_data: float, input_timestamp: float, diff --git a/pycrocart/PyCroCart.py b/pycrocart/PyCroCart.py index 04a0f1f42879985aa2d5f25f66479e80963967ea..70fadb3b241efc413ba571dee681a929579dbb15 100644 --- a/pycrocart/PyCroCart.py +++ b/pycrocart/PyCroCart.py @@ -9,7 +9,8 @@ groundstation and launches the gui. import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QTabWidget, \ - QHBoxLayout, QVBoxLayout, QComboBox, QPushButton, QWidget, QLabel + QHBoxLayout, QVBoxLayout, QComboBox, QPushButton, QWidget, QLabel, \ + QCheckBox, QButtonGroup import uCartCommander from CrazyflieProtoConnection import CrazyflieProtoConnection from ControlTab import ControlTab @@ -47,12 +48,26 @@ class PyCroCart(QMainWindow): self.scan.clicked.connect(self.on_scan) self.scan.setFixedWidth(100) + # Some of the gamepad logic needs to different between crazyflie and + # flypi drones because otherwise it's very hard to control the flypi, + # plus whenever we use a TCP connection for the flypi, + # that connection logic will need to be different as well + self.crazyflie_checkbutton = QCheckBox("Crazyflie") + self.flypi_checkbutton = QCheckBox("FlyPi") + self.crazyflie_checkbutton.setChecked(True) # Default to crazyflie + self.drone_selection_group = QButtonGroup() + self.drone_selection_group.addButton(self.crazyflie_checkbutton, 1) + self.drone_selection_group.addButton(self.flypi_checkbutton, 2) + self.drone_selection_group.setExclusive(True) + self.error_label = QLabel("") button_layout = QHBoxLayout() button_layout.addWidget(self.crazyflie_selection_box) button_layout.addWidget(self.connect) button_layout.addWidget(self.scan) + button_layout.addWidget(self.crazyflie_checkbutton) + button_layout.addWidget(self.flypi_checkbutton) button_layout.addWidget(self.error_label) button_layout.addStretch(1) @@ -109,6 +124,9 @@ class PyCroCart(QMainWindow): def on_connect(self): # check that a crazyflie is selected + self.crazyflie_checkbutton.setEnabled(False) + self.flypi_checkbutton.setEnabled(False) + uri = self.crazyflie_selection_box.currentText() + "/E7E7E7E7E7" # return an error to the user if otherwise @@ -137,6 +155,15 @@ class PyCroCart(QMainWindow): # refresh the logging page so that it displays the toc # refresh the parameter page so that it displays the correct information self.setpoint_handler.setCommander(self.cf.scf.cf.commander) + + # enable restricting the gamepad input for the flypi to be more + # stable or not + # TODO enable more granular gamepad configuration in gamepad config menu + if self.flypi_checkbutton.isChecked(): + self.control_tab.setpoint_menu.enable_flypi_mode() + else: + self.control_tab.setpoint_menu.disable_flypi_mode() + self.logging_config_tab.on_connect() self.parameter_tab.on_connect() @@ -145,6 +172,9 @@ class PyCroCart(QMainWindow): # terminate the link in crazyflieprotoconnection self.cf.disconnect() + self.crazyflie_checkbutton.setEnabled(True) + self.flypi_checkbutton.setEnabled(True) + error_text = "" self.error_label.setText( "<span style='color: green;'>" + error_text + "</span>") @@ -161,6 +191,8 @@ class PyCroCart(QMainWindow): self.parameter_tab.on_disconnect() + + # Start the application if __name__ == '__main__': diff --git a/pycrocart/SetpointHandler.py b/pycrocart/SetpointHandler.py index 4e37777a2ae01e0ddeb3189c438b7c4256ae6f5a..d8f7e56de9b0267d5d64964ae7c54f6448c6a070 100644 --- a/pycrocart/SetpointHandler.py +++ b/pycrocart/SetpointHandler.py @@ -144,7 +144,7 @@ class SetpointHandler: if self._flight_mode == FlightMode.ATTITUDE_TYPE: # scales thrust from 100 for slider control. - thrust = self.setpoint.thrust * 65000 / 100 + thrust = self.setpoint.thrust * 60000 / 100 print(f"Set attitude: {self.setpoint.yaw}, {self.setpoint.pitch}, " f"{self.setpoint.roll}, {thrust}") @@ -154,7 +154,7 @@ class SetpointHandler: elif self._flight_mode == FlightMode.ATTITUDE_RATE_TYPE: - thrust = self.setpoint.thrust * 65000 / 100 + thrust = self.setpoint.thrust * 60000 / 100 print(f"Set attitude rate: {self.setpoint.yaw}," f" {self.setpoint.pitch}, " f"{self.setpoint.roll}, {thrust}") @@ -166,7 +166,7 @@ class SetpointHandler: elif self._flight_mode == FlightMode.MIXED_ATTITUDE_TYPE: # scales thrust from 1000 for more fine-grained gamepad control. - thrust = self.setpoint.thrust * 65000 / 1000 + thrust = self.setpoint.thrust * 60000 / 1000 print(f"Set mixed attitude: {self.setpoint.yaw}," f" {self.setpoint.pitch}, " f"{self.setpoint.roll}, {thrust}") @@ -184,7 +184,7 @@ class SetpointHandler: if self.commander: if self._flight_mode == FlightMode.ATTITUDE_TYPE: # scales thrust from 100 for slider control. - thrust = self.setpoint.thrust * 65000 / 100 + thrust = self.setpoint.thrust * 60000 / 100 print(f"Set attitude: {self.setpoint.yaw}, {self.setpoint.pitch}, " f"{self.setpoint.roll}, {self.setpoint.thrust}") @@ -194,7 +194,7 @@ class SetpointHandler: elif self._flight_mode == FlightMode.ATTITUDE_RATE_TYPE: - thrust = self.setpoint.thrust * 65000 / 100 + thrust = self.setpoint.thrust * 60000 / 100 print(f"Set attitude rate: {self.setpoint.yaw}," f" {self.setpoint.pitch}, " f"{self.setpoint.roll}, {thrust}") @@ -205,7 +205,7 @@ class SetpointHandler: elif self._flight_mode == FlightMode.MIXED_ATTITUDE_TYPE: # scales thrust from 1000 for more fine-grained gamepad control. - thrust = self.setpoint.thrust * 65000 / 1000 + thrust = self.setpoint.thrust * 60000 / 1000 print(f"Set mixed attitude: {self.setpoint.yaw}," f" {self.setpoint.pitch}, " f"{self.setpoint.roll}, {thrust}") diff --git a/pycrocart/SetpointMenu.py b/pycrocart/SetpointMenu.py index 1a14ee2eb7d9093fcd7ae09ddae628269f800b87..775709d5a8fc1ece0218279b398219a8e47a2e2b 100644 --- a/pycrocart/SetpointMenu.py +++ b/pycrocart/SetpointMenu.py @@ -26,9 +26,10 @@ class SetpointMenu(QWidget): self.setpoint_handler = setpoint_handler self.joystick = joystick - self.joystick_reader = DeviceReader(self.joystick) + self.flypi_mode = False + layout = QVBoxLayout() grid_widget = QWidget() @@ -97,7 +98,7 @@ class SetpointMenu(QWidget): self.b2 = QCheckBox("Rate Mode") self.b2.setChecked(True) # Default to rate mode. self.b1.toggled.connect(self.toggle_flight_mode_while_running) - self.b1.toggled.connect(self.toggle_flight_mode_while_running) + self.b2.toggled.connect(self.toggle_flight_mode_while_running) self.attitude_or_rate = QButtonGroup() self.attitude_or_rate.addButton(self.b1, 1) @@ -119,6 +120,12 @@ class SetpointMenu(QWidget): layout.addWidget(self.send_setpoint_button, 3) layout.addWidget(self.stop_flying_button, 4) + def enable_flypi_mode(self): + self.flypi_mode = True + + def disable_flypi_mode(self): + self.flypi_mode = False + @staticmethod def cap_max_value(input_value: float, max_value: float, min_value: float): @@ -174,18 +181,19 @@ class SetpointMenu(QWidget): else: self.setpoint_handler.setRateMode() - if self.gamepad_button.isChecked(): + if self.gamepad_button.isChecked() and self.flypi_mode: yaw = self.cap_max_value(-yaw, 90, -90) pitch = self.cap_max_value(-pitch, 5, -5) roll = self.cap_max_value(-roll, 5, -5) # If you are a flypi do this + # todo enable more granular gamepad configuration in gamepad + # menu thrust = thrust + 270 thrust = self.cap_max_value(thrust, 370, 270) - print("thrust") - print(thrust) - - # otherwise don't..... but I'm not changing that rn + elif self.gamepad_button.isChecked(): + # crazyflie shouldn't be so restricted + thrust = thrust*10 self.setpoint_handler.setSetpoint(yaw, pitch, roll, thrust) @@ -216,7 +224,7 @@ class SetpointMenu(QWidget): self.setpoint.yaw = data.yaw self.setpoint.pitch = data.pitch self.setpoint.roll = data.roll - self.setpoint.thrust = data.thrust + self.setpoint.thrust = data.thrust*1.25 # max thrust comes in at 80 self.setpoint_semaphore.release() @@ -269,6 +277,20 @@ class SetpointMenu(QWidget): self.timer.timeout.disconnect(self.sendGamepadSetpoint) self.timer.stop() + self.setpoint_semaphore.acquire() + + self.setpoint.yaw = 0 + self.setpoint.roll = 0 + self.setpoint.pitch = 0 + self.setpoint.thrust = 0 + + self.yaw_box.setText(str(round(self.setpoint.yaw, 2))) + self.roll_box.setText(str(round(self.setpoint.roll, 2))) + self.pitch_box.setText(str(round(self.setpoint.pitch, 2))) + self.thrust_slider.setValue(int(round(self.setpoint.thrust, 2))) + + self.setpoint_semaphore.release() + self.stop_flying() def sendGamepadSetpoint(self):