Skip to content
Snippets Groups Projects
Commit 7db3ba19 authored by abeinder's avatar abeinder
Browse files

integrated connect button.

Remaining issues:
1) If you unplug the crazyradio while running, the gui needs to return feedback to the user saying this is the case
2) If you try to send a setpoint while no crazyflie is connected, the gui offers no feedback that this is the case
parent 70feae2c
No related branches found
No related tags found
3 merge requests!104adding cflib to this branch,!94Merge cflib adapter into main,!84Pycrocart
......@@ -44,7 +44,7 @@ class CrazyflieProtoConnection:
self.logging_queue = Queue()
self.scf = None
self.is_connected = None
self.is_connected = False
self.param_callback_count = 0
self.logging_configs = []
......@@ -193,11 +193,14 @@ class CrazyflieProtoConnection:
self.scf = SyncCrazyflie(uri, cf=Crazyflie(rw_cache='./cache'))
self.scf.open_link()
self.scf.wait_for_params()
self.is_connected = True
def disconnect(self):
""" Disconnect from crazyflie. """
if self.is_connected:
self.scf.close_link()
self.scf = None
self.is_connected = False
@staticmethod
def list_available_crazyflies():
......
......@@ -112,9 +112,14 @@ class LoggingConfigTab(QWidget):
""" Whenever the refresh button is clicked, call this function. """
self.cf.clear_logging_configs()
self.set_logging_block_from_file()
def on_connect(self):
self.update_logging_values()
self.on_refresh()
self.set_logging_block_from_file()
def on_disconnect(self):
self.update_logging_values()
def on_stop(self):
""" Whenever the stop logging button is clicked, call this function.
......
......@@ -166,9 +166,6 @@ class ParameterTab(QWidget):
self.progress_bar.setMaximumWidth(200)
layout.addWidget(self.progress_bar, 12, 1, alignment=Qt.AlignHCenter)
# for connecting during runtime this would need to be moved
self.on_connect()
def on_connect(self):
""" Whenever connecting to the crazyflie, grab the parameter table of
contents. This is how we actually get what parameters and groups are
......@@ -176,6 +173,10 @@ class ParameterTab(QWidget):
self.toc = self.cf.get_param_toc()
self.populate_group_menu_options()
def on_disconnect(self):
self.toc = {}
self.populate_group_menu_options()
def populate_group_menu_options(self):
""" Remove any current entries from parameter boxes and add the
options for the parameter groups. """
......@@ -192,7 +193,8 @@ class ParameterTab(QWidget):
self.get_param_cbox.clear()
group = self.get_param_group_cbox.currentText()
self.get_param_cbox.addItems(self.toc[group])
if group != "": # nothing there when cf is disconnected
self.get_param_cbox.addItems(self.toc[group])
def on_set_param_group_changed(self):
""" Whenever a parameter group is selected, populate the options for
......@@ -200,7 +202,8 @@ class ParameterTab(QWidget):
self.set_param_cbox.clear()
group = self.set_param_group_cbox.currentText()
self.set_param_cbox.addItems(self.toc[group])
if group != "": # nothing there when cf is disconnected
self.set_param_cbox.addItems(self.toc[group])
def get_param(self):
""" Retrieve parameter value from crazyflie proto connection. All
......
......@@ -8,7 +8,8 @@ groundstation and launches the gui.
"""
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QTabWidget, QHBoxLayout, QVBoxLayout
from PyQt5.QtWidgets import QApplication, QMainWindow, QTabWidget, \
QHBoxLayout, QVBoxLayout, QComboBox, QPushButton, QWidget, QLabel
import uCartCommander
from CrazyflieProtoConnection import CrazyflieProtoConnection
from ControlTab import ControlTab
......@@ -18,6 +19,7 @@ from cfclient.utils.input import JoystickReader
from ParameterTab import ParameterTab
from LoggingConfigTab import LoggingConfigTab
import time
import os
class PyCroCart(QMainWindow):
......@@ -32,44 +34,48 @@ class PyCroCart(QMainWindow):
def __init__(self, cf: CrazyflieProtoConnection,
setpoint_handler: SetpointHandler):
super().__init__()
self.dropdown = QComboBox(self)
self.dropdown.addItem("radio://45/")
self.dropdown.addItem("radio://100/")
self.dropdown.setFixedWidth(150)
self.cf = cf
self.crazyflie_selection_box = QComboBox(self)
self.crazyflie_selection_box.setFixedWidth(150)
self.connect = QPushButton("Connect", self)
self.connect.clicked.connect(self.on_connect)
self.connect.setFixedWidth(100)
self.scan = QPushButton("Scan", self)
self.scan.clicked.connect(self.on_scan)
self.scan.setFixedWidth(100)
self.error_label = QLabel("")
button_layout = QHBoxLayout()
button_layout.addWidget(self.dropdown)
button_layout.addWidget(self.crazyflie_selection_box)
button_layout.addWidget(self.connect)
button_layout.addWidget(self.scan)
button_layout.addWidget(self.error_label)
button_layout.addStretch(1)
self.joystick_reader = JoystickReader()
self.setpoint_handler = setpoint_handler
self.tabs = QTabWidget()
self.tab1 = ControlTab(cf.logging_queue, setpoint_handler,
self.joystick_reader, cf)
self.tab2 = InputConfigDialogue(self.joystick_reader,
self.tab1.setpoint_menu.enableGamepad)
self.tab3 = ParameterTab(cf)
self.tab4 = LoggingConfigTab(
cf, self.tab1.logging_menu.update_available_logging_variables)
self.control_tab = ControlTab(cf.logging_queue, self.setpoint_handler,
self.joystick_reader, cf)
self.gamepad_tab = InputConfigDialogue(
self.joystick_reader, self.control_tab.setpoint_menu.enableGamepad)
self.parameter_tab = ParameterTab(cf)
self.logging_config_tab = LoggingConfigTab(
cf, self.control_tab.logging_menu.update_available_logging_variables)
self.setGeometry(100, 200, 600, 600)
self.tabs.addTab(self.tab1, "Controls Window")
self.tabs.addTab(self.tab2, "Gamepad Configuration")
self.tabs.addTab(self.tab3, "Parameter Window")
self.tabs.addTab(self.tab4, "Logging Window")
self.tabs.addTab(self.control_tab, "Controls Window")
self.tabs.addTab(self.gamepad_tab, "Gamepad Configuration")
self.tabs.addTab(self.parameter_tab, "Parameter Window")
self.tabs.addTab(self.logging_config_tab, "Logging Window")
main_layout = QHBoxLayout()
main_layout = QVBoxLayout()
main_layout.addLayout(button_layout)
main_layout.addWidget(self.tabs)
......@@ -77,7 +83,83 @@ class PyCroCart(QMainWindow):
widget.setLayout(main_layout)
self.setCentralWidget(widget)
# self.show()
def on_scan(self):
""" When pressing the scan button, connect to the crazyradio,
and scan for crazyflies. Populate the crazyflie selection box. """
cfs = self.cf.list_available_crazyflies()
# cfs = ["radio://45/", "radio://100/"] # for development purposes only
if cfs:
error_text = ""
self.error_label.setText(
"<span style='color: red;'>" + error_text + "</span>")
# add crazyflie connections to selection box
self.crazyflie_selection_box.clear()
self.crazyflie_selection_box.addItems(cfs[0][:len(cfs[0])-1])
else:
error_text = "Either no crazyradio plugged in, or no crazyflies " \
"detected."
self.error_label.setText(
"<span style='color: red;'>" + error_text + "</span>")
# clear out selection box
self.crazyflie_selection_box.clear()
def on_connect(self):
# check that a crazyflie is selected
uri = self.crazyflie_selection_box.currentText() + "/E7E7E7E7E7"
# return an error to the user if otherwise
if uri == "/E7E7E7E7E7":
error_text = "No crazyflie selected."
self.error_label.setText(
"<span style='color: red;'>" + error_text + "</span>")
# attempt to connect to the crazyflie
self.cf.connect(uri)
self.cf.scf.cf.commander = uCartCommander.Commander(cf1.scf.cf)
self.cf.scf.wait_for_params()
# offer the user a green error label saying connected when connected
error_text = "Connected to drone"
self.error_label.setText(
"<span style='color: green;'>" + error_text + "</span>")
# change the connect button to a disconnect button, and disconnect this
# function from it and connect to the on_disconnect function
self.connect.clicked.disconnect(self.on_connect)
self.connect.clicked.connect(self.on_disconnect)
self.connect.setText("Disconnect")
# connect the crazyflie commander to the setpoint handler
# 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)
self.logging_config_tab.on_connect()
self.parameter_tab.on_connect()
def on_disconnect(self):
# terminate the link in crazyflieprotoconnection
self.cf.disconnect()
error_text = ""
self.error_label.setText(
"<span style='color: green;'>" + error_text + "</span>")
# change the disconnect button to a connect button, and disconnect this
# function from it and connect the on_connect function
self.connect.clicked.disconnect(self.on_disconnect)
self.connect.clicked.connect(self.on_connect)
self.connect.setText("Connect")
# disconnect the commander from setpointhandler
self.setpoint_handler.disconnectCommander()
self.logging_config_tab.on_disconnect()
self.parameter_tab.on_disconnect()
# Start the application
......@@ -86,15 +168,10 @@ if __name__ == '__main__':
app = QApplication(sys.argv)
cf1 = CrazyflieProtoConnection()
uri = 'radio://0/60/2M/E7E7E7E7E7'
cf1.connect(uri)
cf1.scf.cf.commander = uCartCommander.Commander(cf1.scf.cf)
time.sleep(1)
setpoint_handler1 = SetpointHandler(cf1.scf.cf.commander)
setpoint_handler1 = SetpointHandler()
window = PyCroCart(cf1, setpoint_handler1)
window.tab4.on_refresh()
window.show()
sys.exit(app.exec_())
# Janky but it does work
os._exit(app.exec_())
......@@ -47,7 +47,7 @@ class SetpointHandler:
"""
def __init__(self, commander: Commander):
def __init__(self):
"""
Initialize timer,
......@@ -56,7 +56,7 @@ class SetpointHandler:
"""
self.setpoint = Setpoint()
self.commander = commander
self.commander = None
self.setpoint_semaphore = Semaphore(1)
self._flight_mode = FlightMode.TYPE_STOP
......@@ -66,6 +66,17 @@ class SetpointHandler:
self.timer.timeout.connect(self.update)
self.timer.start(20)
def setCommander(self, commander: Commander):
""" When the crazyflie is not connected, there will be no valid
commander. Set it during runtime. Enables the use of if commander:
on other functions to check if it is valid. """
self.commander = commander
def disconnectCommander(self):
""" Set the commander equal to none so that it won't be called when
we are not actually connected to the crazyflie. """
self.commander = None
def update(self):
""" If the flight mode is not stopped, send the current setpoint. """
if self._flight_mode != FlightMode.TYPE_STOP:
......@@ -99,16 +110,17 @@ class SetpointHandler:
def startFlying(self):
""" Sends an all 0's setpoint which is the convention to tell the
crazyflie to listen for setpoints. """
self.commander.start_flying()
if self.commander:
self.commander.start_flying()
def stopFlying(self):
""" Tells the crazyflie to stop flying. """
self.setpoint_semaphore.acquire()
self._flight_mode = FlightMode.TYPE_STOP
if self.commander:
self.setpoint_semaphore.acquire()
self._flight_mode = FlightMode.TYPE_STOP
self.commander.send_notify_setpoint_stop(0)
self.setpoint_semaphore.release()
print("Stop flying")
self.commander.send_notify_setpoint_stop(0)
self.setpoint_semaphore.release()
def setSetpoint(self, yaw: float, pitch: float, roll: float, thrust: float):
""" Safely sets the crazyflie setpoint. Utilizes semaphore to avoid
......@@ -126,76 +138,78 @@ class SetpointHandler:
def sendSetpoint(self):
""" Uses commander to send setpoints to crazyflie depending upon the
current flight mode. """
self.setpoint_semaphore.acquire()
if self.commander:
self.setpoint_semaphore.acquire()
if self._flight_mode == FlightMode.ATTITUDE_TYPE:
if self._flight_mode == FlightMode.ATTITUDE_TYPE:
# scales thrust from 100 for slider control.
thrust = self.setpoint.thrust * 65000 / 100
print(f"Set attitude: {self.setpoint.yaw}, {self.setpoint.pitch}, "
f"{self.setpoint.roll}, {thrust}")
# scales thrust from 100 for slider control.
thrust = self.setpoint.thrust * 65000 / 100
print(f"Set attitude: {self.setpoint.yaw}, {self.setpoint.pitch}, "
f"{self.setpoint.roll}, {thrust}")
self.commander.send_attitude_setpoint(
self.setpoint.yaw, self.setpoint.pitch, self.setpoint.roll,
thrust)
self.commander.send_attitude_setpoint(
self.setpoint.yaw, self.setpoint.pitch, self.setpoint.roll,
thrust)
elif self._flight_mode == FlightMode.ATTITUDE_RATE_TYPE:
elif self._flight_mode == FlightMode.ATTITUDE_RATE_TYPE:
thrust = self.setpoint.thrust * 65000 / 100
print(f"Set attitude rate: {self.setpoint.yaw},"
f" {self.setpoint.pitch}, "
f"{self.setpoint.roll}, {thrust}")
thrust = self.setpoint.thrust * 65000 / 100
print(f"Set attitude rate: {self.setpoint.yaw},"
f" {self.setpoint.pitch}, "
f"{self.setpoint.roll}, {thrust}")
self.commander.send_attitude_rate_setpoint(
self.setpoint.yaw, self.setpoint.pitch, self.setpoint.roll,
thrust)
self.commander.send_attitude_rate_setpoint(
self.setpoint.yaw, self.setpoint.pitch, self.setpoint.roll,
thrust)
elif self._flight_mode == FlightMode.MIXED_ATTITUDE_TYPE:
elif self._flight_mode == FlightMode.MIXED_ATTITUDE_TYPE:
# scales thrust from 1000 for more fine-grained gamepad control.
thrust = self.setpoint.thrust * 65000 / 1000
print(f"Set mixed attitude: {self.setpoint.yaw},"
f" {self.setpoint.pitch}, "
f"{self.setpoint.roll}, {thrust}")
# scales thrust from 1000 for more fine-grained gamepad control.
thrust = self.setpoint.thrust * 65000 / 1000
print(f"Set mixed attitude: {self.setpoint.yaw},"
f" {self.setpoint.pitch}, "
f"{self.setpoint.roll}, {thrust}")
self.commander.send_mixed_attitude_setpoint(
self.setpoint.yaw, self.setpoint.pitch, self.setpoint.roll,
thrust)
self.commander.send_mixed_attitude_setpoint(
self.setpoint.yaw, self.setpoint.pitch, self.setpoint.roll,
thrust)
self.setpoint_semaphore.release()
self.setpoint_semaphore.release()
def sendSetpointUnsafe(self):
""" Exactly the same as send setpoint but no semaphore is used. """
print("Unsafe mode activate :)")
if self._flight_mode == FlightMode.ATTITUDE_TYPE:
# scales thrust from 100 for slider control.
thrust = self.setpoint.thrust * 65000 / 100
print(f"Set attitude: {self.setpoint.yaw}, {self.setpoint.pitch}, "
f"{self.setpoint.roll}, {self.setpoint.thrust}")
self.commander.send_attitude_setpoint(
self.setpoint.yaw, self.setpoint.pitch, self.setpoint.roll,
int(thrust))
elif self._flight_mode == FlightMode.ATTITUDE_RATE_TYPE:
thrust = self.setpoint.thrust * 65000 / 100
print(f"Set attitude rate: {self.setpoint.yaw},"
f" {self.setpoint.pitch}, "
f"{self.setpoint.roll}, {thrust}")
self.commander.send_attitude_rate_setpoint(
self.setpoint.yaw, self.setpoint.pitch, self.setpoint.roll,
thrust)
elif self._flight_mode == FlightMode.MIXED_ATTITUDE_TYPE:
# scales thrust from 1000 for more fine-grained gamepad control.
thrust = self.setpoint.thrust * 65000 / 1000
print(f"Set mixed attitude: {self.setpoint.yaw},"
f" {self.setpoint.pitch}, "
f"{self.setpoint.roll}, {thrust}")
self.commander.send_mixed_attitude_setpoint(
self.setpoint.yaw, self.setpoint.pitch, self.setpoint.roll,
thrust)
if self.commander:
if self._flight_mode == FlightMode.ATTITUDE_TYPE:
# scales thrust from 100 for slider control.
thrust = self.setpoint.thrust * 65000 / 100
print(f"Set attitude: {self.setpoint.yaw}, {self.setpoint.pitch}, "
f"{self.setpoint.roll}, {self.setpoint.thrust}")
self.commander.send_attitude_setpoint(
self.setpoint.yaw, self.setpoint.pitch, self.setpoint.roll,
int(thrust))
elif self._flight_mode == FlightMode.ATTITUDE_RATE_TYPE:
thrust = self.setpoint.thrust * 65000 / 100
print(f"Set attitude rate: {self.setpoint.yaw},"
f" {self.setpoint.pitch}, "
f"{self.setpoint.roll}, {thrust}")
self.commander.send_attitude_rate_setpoint(
self.setpoint.yaw, self.setpoint.pitch, self.setpoint.roll,
thrust)
elif self._flight_mode == FlightMode.MIXED_ATTITUDE_TYPE:
# scales thrust from 1000 for more fine-grained gamepad control.
thrust = self.setpoint.thrust * 65000 / 1000
print(f"Set mixed attitude: {self.setpoint.yaw},"
f" {self.setpoint.pitch}, "
f"{self.setpoint.roll}, {thrust}")
self.commander.send_mixed_attitude_setpoint(
self.setpoint.yaw, self.setpoint.pitch, self.setpoint.roll,
thrust)
......@@ -163,7 +163,8 @@ class SetpointMenu(QWidget):
# into the right control mode, and also tell the crazyflie we are
# about to start flying.
if self.setpoint_handler.getFlightMode() == FlightMode.TYPE_STOP:
self.setpoint_handler.commander.start_flying()
if self.setpoint_handler.commander: # check if connected
self.setpoint_handler.commander.start_flying()
if self.gamepad_button.isChecked():
self.setpoint_handler.setMixedAttitudeMode()
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment