cfclient 2017.4__py3-none-any.whl → 2025.12.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cfclient/__init__.py +16 -11
- cfclient/configs/config.json +4 -3
- cfclient/configs/input/Generic_OS_X.json +1 -0
- cfclient/configs/input/Joystick.json +1 -0
- cfclient/configs/input/PS3_Mode_1.json +1 -0
- cfclient/configs/input/PS3_Mode_2.json +1 -0
- cfclient/configs/input/PS3_Mode_3.json +1 -0
- cfclient/configs/input/PS4_Mode_1.json +1 -0
- cfclient/configs/input/PS4_Mode_2.json +1 -0
- cfclient/configs/input/PS4_shoulder_btns_yaw.json +1 -0
- cfclient/configs/input/xbox360_mode1.json +1 -0
- cfclient/configs/log/PID_tuning/Attitude.json +46 -0
- cfclient/configs/log/PID_tuning/Attitude_rate.json +46 -0
- cfclient/configs/log/PID_tuning/Position.json +46 -0
- cfclient/configs/log/PID_tuning/Velocity.json +46 -0
- cfclient/configs/log/PID_tuning_components/Pitch.json +22 -0
- cfclient/configs/log/PID_tuning_components/Pitch_rate.json +22 -0
- cfclient/configs/log/PID_tuning_components/Position_x.json +22 -0
- cfclient/configs/log/PID_tuning_components/Position_y.json +22 -0
- cfclient/configs/log/PID_tuning_components/Position_z.json +22 -0
- cfclient/configs/log/PID_tuning_components/Roll.json +22 -0
- cfclient/configs/log/PID_tuning_components/Roll_rate.json +22 -0
- cfclient/configs/log/PID_tuning_components/Velocity_x.json +22 -0
- cfclient/configs/log/PID_tuning_components/Velocity_y.json +22 -0
- cfclient/configs/log/PID_tuning_components/Velocity_z.json +22 -0
- cfclient/configs/log/PID_tuning_components/Yaw.json +22 -0
- cfclient/configs/log/PID_tuning_components/Yaw_rate.json +22 -0
- cfclient/gui.py +44 -9
- cfclient/headless.py +3 -12
- cfclient/resources/log_param_doc.json +1 -0
- cfclient/ui/connectivity_manager.py +198 -0
- cfclient/ui/dialogs/about.py +53 -36
- cfclient/ui/dialogs/about.ui +23 -3
- cfclient/ui/dialogs/anchor_position_dialog.py +252 -0
- cfclient/ui/dialogs/anchor_position_dialog.ui +138 -0
- cfclient/ui/dialogs/basestation_mode_dialog.py +185 -0
- cfclient/ui/dialogs/basestation_mode_dialog.ui +186 -0
- cfclient/ui/dialogs/bootloader.py +448 -85
- cfclient/ui/dialogs/bootloader.ui +387 -134
- cfclient/ui/dialogs/cf2config.py +4 -4
- cfclient/ui/dialogs/cf2config.ui +3 -4
- cfclient/ui/dialogs/inputconfigdialogue.py +24 -19
- cfclient/ui/dialogs/inputconfigdialogue.ui +53 -30
- cfclient/ui/dialogs/lighthouse_bs_geometry_dialog.py +220 -0
- cfclient/ui/dialogs/lighthouse_bs_geometry_dialog.ui +110 -0
- cfclient/ui/dialogs/lighthouse_system_type_dialog.py +93 -0
- cfclient/ui/dialogs/lighthouse_system_type_dialog.ui +121 -0
- cfclient/ui/dialogs/logconfigdialogue.py +401 -101
- cfclient/ui/dialogs/logconfigdialogue.ui +117 -72
- cfclient/ui/icons/bl.webp +0 -0
- cfclient/ui/icons/bolt.webp +0 -0
- cfclient/ui/icons/cf21.webp +0 -0
- cfclient/ui/icons/checkmark_black.png +0 -0
- cfclient/ui/icons/checkmark_white.png +0 -0
- cfclient/ui/icons/create.png +0 -0
- cfclient/ui/icons/delete.png +0 -0
- cfclient/ui/icons/flapper.webp +0 -0
- cfclient/ui/icons/tag.webp +0 -0
- cfclient/ui/main.py +328 -258
- cfclient/ui/main.ui +184 -80
- cfclient/ui/pluginhelper.py +7 -1
- cfclient/ui/pose_logger.py +116 -0
- cfclient/ui/tab_toolbox.py +208 -0
- cfclient/ui/tabs/ColorLEDTab.py +752 -0
- cfclient/ui/tabs/ConsoleTab.py +48 -13
- cfclient/ui/{toolboxes → tabs}/CrtpSharkToolbox.py +19 -34
- cfclient/ui/tabs/ExampleTab.py +9 -16
- cfclient/ui/tabs/FlightTab.py +437 -325
- cfclient/ui/tabs/GpsTab.py +14 -20
- cfclient/ui/tabs/LEDRingTab.py +277 -0
- cfclient/ui/tabs/LogBlockDebugTab.py +20 -27
- cfclient/ui/tabs/LogBlockTab.py +35 -35
- cfclient/ui/tabs/LogClientTab.py +85 -0
- cfclient/ui/tabs/LogTab.py +50 -27
- cfclient/ui/tabs/ParamTab.py +443 -57
- cfclient/ui/tabs/PlotTab.py +23 -25
- cfclient/ui/tabs/TuningTab.py +292 -0
- cfclient/ui/tabs/__init__.py +12 -2
- cfclient/ui/tabs/colorLEDTab.ui +624 -0
- cfclient/ui/tabs/consoleTab.ui +46 -0
- cfclient/ui/tabs/flightActionContainer.ui +103 -0
- cfclient/ui/tabs/flightTab.ui +724 -237
- cfclient/ui/tabs/{ledTab.ui → ledRingTab.ui} +63 -46
- cfclient/ui/tabs/lighthouse_tab.py +714 -0
- cfclient/ui/tabs/lighthouse_tab.ui +430 -0
- cfclient/ui/tabs/locopositioning_tab.py +606 -389
- cfclient/ui/tabs/locopositioning_tab.ui +370 -253
- cfclient/ui/tabs/logClientTab.ui +52 -0
- cfclient/ui/tabs/logTab.ui +1 -1
- cfclient/ui/tabs/paramTab.ui +204 -3
- cfclient/ui/tabs/tuningTab.ui +773 -0
- cfclient/ui/widgets/ai.py +37 -39
- cfclient/ui/widgets/hexspinbox.py +16 -10
- cfclient/ui/widgets/plotter.ui +39 -47
- cfclient/ui/widgets/plotwidget.py +57 -22
- cfclient/ui/widgets/super_slider.py +112 -0
- cfclient/ui/wizards/__init__.py +0 -0
- cfclient/ui/wizards/bslh_1.png +0 -0
- cfclient/ui/wizards/bslh_2.png +0 -0
- cfclient/ui/wizards/bslh_3.png +0 -0
- cfclient/ui/wizards/bslh_4.png +0 -0
- cfclient/ui/wizards/bslh_5.png +0 -0
- cfclient/ui/wizards/lighthouse_geo_bs_estimation_wizard.py +465 -0
- cfclient/utils/config_manager.py +5 -4
- cfclient/utils/input/__init__.py +77 -19
- cfclient/utils/input/inputinterfaces/wiimote.py +2 -2
- cfclient/utils/input/inputreaderinterface.py +17 -7
- cfclient/utils/input/inputreaders/__init__.py +17 -0
- cfclient/utils/logconfigreader.py +245 -25
- cfclient/utils/logdatawriter.py +3 -1
- cfclient/utils/periodictimer.py +1 -1
- cfclient/utils/ui.py +336 -0
- cfclient/utils/zmq_led_driver.py +5 -0
- cfclient/utils/zmq_param.py +6 -0
- cfclient/version.py +34 -1
- cfclient-2025.12.1.dist-info/METADATA +70 -0
- cfclient-2025.12.1.dist-info/RECORD +152 -0
- {cfclient-2017.4.dist-info → cfclient-2025.12.1.dist-info}/WHEEL +1 -1
- {cfclient-2017.4.dist-info → cfclient-2025.12.1.dist-info}/entry_points.txt +0 -1
- cfclient-2025.12.1.dist-info/licenses/LICENSE.txt +350 -0
- {cfclient-2017.4.dist-info → cfclient-2025.12.1.dist-info}/top_level.txt +1 -0
- cfconfig/Makefile +51 -0
- cfconfig/configblock.py +111 -0
- cfloader/__init__.py +41 -55
- cfzmq/__init__.py +22 -14
- cfclient/ui/dialogs/cf1config.py +0 -265
- cfclient/ui/dialogs/cf1config.ui +0 -260
- cfclient/ui/tab.py +0 -96
- cfclient/ui/tabs/LEDTab.py +0 -169
- cfclient/ui/toolboxes/ConsoleToolbox.py +0 -69
- cfclient/ui/toolboxes/DebugDriverToolbox.py +0 -107
- cfclient/ui/toolboxes/__init__.py +0 -45
- cfclient/ui/toolboxes/consoleToolbox.ui +0 -62
- cfclient/ui/toolboxes/debugDriverToolbox.ui +0 -86
- cfclient-2017.4.dist-info/DESCRIPTION.rst +0 -3
- cfclient-2017.4.dist-info/METADATA +0 -22
- cfclient-2017.4.dist-info/RECORD +0 -104
- cfclient-2017.4.dist-info/metadata.json +0 -1
- /cfclient/{icon-256.png → ui/icons/icon-256.png} +0 -0
- /cfclient/ui/{toolboxes → tabs}/crtpSharkToolbox.ui +0 -0
cfclient/ui/main.py
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# +------+ / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
|
|
8
8
|
# || || /_____/_/\__/\___/_/ \__,_/ /___/\___/
|
|
9
9
|
#
|
|
10
|
-
# Copyright (C) 2011-
|
|
10
|
+
# Copyright (C) 2011-2023 Bitcraze AB
|
|
11
11
|
#
|
|
12
12
|
# Crazyflie Nano Quadcopter Client
|
|
13
13
|
#
|
|
@@ -28,61 +28,58 @@ The main file for the Crazyflie control application.
|
|
|
28
28
|
"""
|
|
29
29
|
import logging
|
|
30
30
|
import sys
|
|
31
|
+
import usb
|
|
31
32
|
|
|
32
33
|
import cfclient
|
|
34
|
+
from cfclient.ui.pose_logger import PoseLogger
|
|
35
|
+
from cfclient.ui.tab_toolbox import TabToolbox
|
|
33
36
|
import cfclient.ui.tabs
|
|
34
|
-
import cfclient.ui.toolboxes
|
|
35
37
|
import cflib.crtp
|
|
36
38
|
from cfclient.ui.dialogs.about import AboutDialog
|
|
37
39
|
from cfclient.ui.dialogs.bootloader import BootloaderDialog
|
|
40
|
+
from cfclient.ui.connectivity_manager import ConnectivityManager
|
|
38
41
|
from cfclient.utils.config import Config
|
|
39
42
|
from cfclient.utils.config_manager import ConfigManager
|
|
40
43
|
from cfclient.utils.input import JoystickReader
|
|
41
44
|
from cfclient.utils.logconfigreader import LogConfigReader
|
|
45
|
+
from cfclient.utils.ui import UiUtils
|
|
42
46
|
from cfclient.utils.zmq_led_driver import ZMQLEDDriver
|
|
43
47
|
from cfclient.utils.zmq_param import ZMQParamAccess
|
|
44
48
|
from cflib.crazyflie import Crazyflie
|
|
45
49
|
from cflib.crazyflie.log import LogConfig
|
|
46
50
|
from cflib.crazyflie.mem import MemoryElement
|
|
47
|
-
from
|
|
48
|
-
from
|
|
49
|
-
from
|
|
50
|
-
from
|
|
51
|
-
from
|
|
52
|
-
from
|
|
53
|
-
from
|
|
54
|
-
from
|
|
55
|
-
from
|
|
56
|
-
from
|
|
57
|
-
from
|
|
58
|
-
from
|
|
59
|
-
from
|
|
60
|
-
|
|
61
|
-
from .
|
|
51
|
+
from PyQt6 import QtWidgets
|
|
52
|
+
from PyQt6 import uic
|
|
53
|
+
from PyQt6.QtCore import pyqtSignal
|
|
54
|
+
from PyQt6.QtCore import pyqtSlot
|
|
55
|
+
from PyQt6.QtCore import QDir
|
|
56
|
+
from PyQt6.QtCore import QThread
|
|
57
|
+
from PyQt6.QtCore import QUrl
|
|
58
|
+
from PyQt6.QtCore import Qt
|
|
59
|
+
from PyQt6.QtGui import QAction
|
|
60
|
+
from PyQt6.QtGui import QActionGroup
|
|
61
|
+
from PyQt6.QtGui import QShortcut
|
|
62
|
+
from PyQt6.QtGui import QDesktopServices
|
|
63
|
+
from PyQt6.QtGui import QPalette
|
|
64
|
+
from PyQt6.QtWidgets import QLabel
|
|
65
|
+
from PyQt6.QtWidgets import QMenu
|
|
66
|
+
from PyQt6.QtWidgets import QMessageBox
|
|
67
|
+
|
|
62
68
|
from .dialogs.cf2config import Cf2ConfigDialog
|
|
63
69
|
from .dialogs.inputconfigdialogue import InputConfigDialogue
|
|
64
70
|
from .dialogs.logconfigdialogue import LogConfigDialogue
|
|
65
71
|
|
|
72
|
+
|
|
66
73
|
__author__ = 'Bitcraze AB'
|
|
67
74
|
__all__ = ['MainUI']
|
|
68
75
|
|
|
69
76
|
logger = logging.getLogger(__name__)
|
|
70
77
|
|
|
71
|
-
INTERFACE_PROMPT_TEXT = 'Select an interface'
|
|
72
|
-
|
|
73
78
|
(main_window_class,
|
|
74
79
|
main_windows_base_class) = (uic.loadUiType(cfclient.module_path +
|
|
75
80
|
'/ui/main.ui'))
|
|
76
81
|
|
|
77
82
|
|
|
78
|
-
class MyDockWidget(QtWidgets.QDockWidget):
|
|
79
|
-
closed = pyqtSignal()
|
|
80
|
-
|
|
81
|
-
def closeEvent(self, event):
|
|
82
|
-
super(MyDockWidget, self).closeEvent(event)
|
|
83
|
-
self.closed.emit()
|
|
84
|
-
|
|
85
|
-
|
|
86
83
|
class UIState:
|
|
87
84
|
DISCONNECTED = 0
|
|
88
85
|
CONNECTING = 1
|
|
@@ -94,24 +91,6 @@ class BatteryStates:
|
|
|
94
91
|
BATTERY, CHARGING, CHARGED, LOW_POWER = list(range(4))
|
|
95
92
|
|
|
96
93
|
|
|
97
|
-
COLOR_BLUE = '#3399ff'
|
|
98
|
-
COLOR_GREEN = '#00ff60'
|
|
99
|
-
COLOR_RED = '#cc0404'
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def progressbar_stylesheet(color):
|
|
103
|
-
return """
|
|
104
|
-
QProgressBar {
|
|
105
|
-
border: 1px solid #333;
|
|
106
|
-
background-color: transparent;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
QProgressBar::chunk {
|
|
110
|
-
background-color: """ + color + """;
|
|
111
|
-
}
|
|
112
|
-
"""
|
|
113
|
-
|
|
114
|
-
|
|
115
94
|
class MainUI(QtWidgets.QMainWindow, main_window_class):
|
|
116
95
|
connectionLostSignal = pyqtSignal(str, str)
|
|
117
96
|
connectionInitiatedSignal = pyqtSignal(str)
|
|
@@ -119,7 +98,7 @@ class MainUI(QtWidgets.QMainWindow, main_window_class):
|
|
|
119
98
|
connectionDoneSignal = pyqtSignal(str)
|
|
120
99
|
connectionFailedSignal = pyqtSignal(str, str)
|
|
121
100
|
disconnectedSignal = pyqtSignal(str)
|
|
122
|
-
linkQualitySignal = pyqtSignal(
|
|
101
|
+
linkQualitySignal = pyqtSignal(float)
|
|
123
102
|
|
|
124
103
|
_input_device_error_signal = pyqtSignal(str)
|
|
125
104
|
_input_discovery_signal = pyqtSignal(object)
|
|
@@ -136,47 +115,10 @@ class MainUI(QtWidgets.QMainWindow, main_window_class):
|
|
|
136
115
|
except KeyError:
|
|
137
116
|
pass
|
|
138
117
|
|
|
139
|
-
######################################################
|
|
140
|
-
# By lxrocks
|
|
141
|
-
# 'Skinny Progress Bar' tweak for Yosemite
|
|
142
|
-
# Tweak progress bar - artistic I am not - so pick your own colors !!!
|
|
143
|
-
# Only apply to Yosemite
|
|
144
|
-
######################################################
|
|
145
|
-
import platform
|
|
146
|
-
|
|
147
|
-
if platform.system() == 'Darwin':
|
|
148
|
-
|
|
149
|
-
(Version, junk, machine) = platform.mac_ver()
|
|
150
|
-
logger.info("This is a MAC - checking if we can apply Progress "
|
|
151
|
-
"Bar Stylesheet for Yosemite Skinny Bars ")
|
|
152
|
-
yosemite = (10, 10, 0)
|
|
153
|
-
tVersion = tuple(map(int, (Version.split("."))))
|
|
154
|
-
|
|
155
|
-
if tVersion >= yosemite:
|
|
156
|
-
logger.info("Found Yosemite - applying stylesheet")
|
|
157
|
-
|
|
158
|
-
tcss = """
|
|
159
|
-
QProgressBar {
|
|
160
|
-
border: 1px solid grey;
|
|
161
|
-
border-radius: 5px;
|
|
162
|
-
text-align: center;
|
|
163
|
-
}
|
|
164
|
-
QProgressBar::chunk {
|
|
165
|
-
background-color: """ + COLOR_BLUE + """;
|
|
166
|
-
}
|
|
167
|
-
"""
|
|
168
|
-
self.setStyleSheet(tcss)
|
|
169
|
-
|
|
170
|
-
else:
|
|
171
|
-
logger.info("Pre-Yosemite - skinny bar stylesheet not applied")
|
|
172
|
-
|
|
173
|
-
######################################################
|
|
174
|
-
|
|
175
118
|
self.cf = Crazyflie(ro_cache=None,
|
|
176
119
|
rw_cache=cfclient.config_path + "/cache")
|
|
177
120
|
|
|
178
|
-
cflib.crtp.init_drivers(
|
|
179
|
-
.get("enable_debug_driver"))
|
|
121
|
+
cflib.crtp.init_drivers()
|
|
180
122
|
|
|
181
123
|
zmq_params = ZMQParamAccess(self.cf)
|
|
182
124
|
zmq_params.start()
|
|
@@ -193,15 +135,26 @@ class MainUI(QtWidgets.QMainWindow, main_window_class):
|
|
|
193
135
|
" fly.")
|
|
194
136
|
self.statusBar().addWidget(self._statusbar_label)
|
|
195
137
|
|
|
138
|
+
#
|
|
139
|
+
# We use this hacky-trick to find out if we are in dark-mode and
|
|
140
|
+
# figure out what bgcolor to set from that. We always use the current
|
|
141
|
+
# palette forgreound.
|
|
142
|
+
#
|
|
143
|
+
self.textColor = self._statusbar_label.palette().color(QPalette.ColorRole.WindowText)
|
|
144
|
+
self.bgColor = self._statusbar_label.palette().color(QPalette.ColorRole.Window)
|
|
145
|
+
self.isDark = self.textColor.value() > self.bgColor.value()
|
|
146
|
+
|
|
196
147
|
self.joystickReader = JoystickReader()
|
|
197
148
|
self._active_device = ""
|
|
198
149
|
# self.configGroup = QActionGroup(self._menu_mappings, exclusive=True)
|
|
199
150
|
|
|
200
|
-
self._mux_group = QActionGroup(self._menu_inputdevice
|
|
151
|
+
self._mux_group = QActionGroup(self._menu_inputdevice)
|
|
152
|
+
self._mux_group.setExclusive(True)
|
|
201
153
|
|
|
202
154
|
# TODO: Need to reload configs
|
|
203
155
|
# ConfigManager().conf_needs_reload.add_callback(self._reload_configs)
|
|
204
156
|
|
|
157
|
+
self.connect_input = QShortcut("Ctrl+I", self.connectButton, self._connect)
|
|
205
158
|
self.cf.connection_failed.add_callback(
|
|
206
159
|
self.connectionFailedSignal.emit)
|
|
207
160
|
self.connectionFailedSignal.connect(self._connection_failed)
|
|
@@ -214,17 +167,8 @@ class MainUI(QtWidgets.QMainWindow, main_window_class):
|
|
|
214
167
|
self.joystickReader.device_discovery.add_callback(
|
|
215
168
|
self._input_discovery_signal.emit)
|
|
216
169
|
|
|
217
|
-
# Hide the 'File' menu on OS X, since its only item, 'Exit', gets
|
|
218
|
-
# merged into the application menu.
|
|
219
|
-
if sys.platform == 'darwin':
|
|
220
|
-
self.menuFile.menuAction().setVisible(False)
|
|
221
|
-
|
|
222
170
|
# Connect UI signals
|
|
223
171
|
self.logConfigAction.triggered.connect(self._show_connect_dialog)
|
|
224
|
-
self.interfaceCombo.currentIndexChanged['QString'].connect(
|
|
225
|
-
self.interfaceChanged)
|
|
226
|
-
self.connectButton.clicked.connect(self._connect)
|
|
227
|
-
self.scanButton.clicked.connect(self._scan)
|
|
228
172
|
self.menuItemConnect.triggered.connect(self._connect)
|
|
229
173
|
self.menuItemConfInputDevice.triggered.connect(
|
|
230
174
|
self._show_input_device_config_dialog)
|
|
@@ -234,21 +178,38 @@ class MainUI(QtWidgets.QMainWindow, main_window_class):
|
|
|
234
178
|
self._menuItem_openconfigfolder.triggered.connect(
|
|
235
179
|
self._open_config_folder)
|
|
236
180
|
|
|
237
|
-
self.
|
|
181
|
+
self._set_address()
|
|
182
|
+
|
|
183
|
+
self._connectivity_manager = ConnectivityManager()
|
|
184
|
+
self._connectivity_manager.register_ui_elements(
|
|
185
|
+
ConnectivityManager.UiElementsContainer(
|
|
186
|
+
interface_combo=self.interfaceCombo,
|
|
187
|
+
address_spinner=self.address,
|
|
188
|
+
connect_button=self.connectButton,
|
|
189
|
+
scan_button=self.scanButton))
|
|
190
|
+
|
|
191
|
+
self._connectivity_manager.connect_button_clicked.connect(self._connect)
|
|
192
|
+
self._connectivity_manager.scan_button_clicked.connect(self._scan_from_button)
|
|
238
193
|
|
|
239
|
-
self.
|
|
240
|
-
self.autoReconnectCheckBox.toggled.connect(
|
|
241
|
-
self._auto_reconnect_changed)
|
|
242
|
-
self.autoReconnectCheckBox.setChecked(Config().get("auto_reconnect"))
|
|
194
|
+
self._disable_input = False
|
|
243
195
|
|
|
244
196
|
self.joystickReader.input_updated.add_callback(
|
|
245
|
-
self.
|
|
197
|
+
lambda *args: self._disable_input or
|
|
198
|
+
self.cf.commander.send_setpoint(*args))
|
|
246
199
|
|
|
247
200
|
self.joystickReader.assisted_input_updated.add_callback(
|
|
248
|
-
self.
|
|
201
|
+
lambda *args: self._disable_input or
|
|
202
|
+
self.cf.commander.send_velocity_world_setpoint(*args))
|
|
249
203
|
|
|
250
204
|
self.joystickReader.heighthold_input_updated.add_callback(
|
|
251
|
-
self.
|
|
205
|
+
lambda *args: self._disable_input or
|
|
206
|
+
self.cf.commander.send_zdistance_setpoint(*args))
|
|
207
|
+
|
|
208
|
+
self.joystickReader.hover_input_updated.add_callback(
|
|
209
|
+
self.cf.commander.send_hover_setpoint)
|
|
210
|
+
|
|
211
|
+
# Emergency stop button
|
|
212
|
+
self.esButton.clicked.connect(self._emergency_stop)
|
|
252
213
|
|
|
253
214
|
# Connection callbacks and signal wrappers for UI protection
|
|
254
215
|
self.cf.connected.add_callback(self.connectionDoneSignal.emit)
|
|
@@ -263,19 +224,12 @@ class MainUI(QtWidgets.QMainWindow, main_window_class):
|
|
|
263
224
|
self._log_error_signal.connect(self._logging_error)
|
|
264
225
|
|
|
265
226
|
self.batteryBar.setTextVisible(False)
|
|
266
|
-
self.batteryBar.setStyleSheet(progressbar_stylesheet(COLOR_BLUE))
|
|
267
|
-
|
|
268
227
|
self.linkQualityBar.setTextVisible(False)
|
|
269
|
-
self.linkQualityBar.setStyleSheet(progressbar_stylesheet(COLOR_BLUE))
|
|
270
228
|
|
|
271
229
|
# Connect link quality feedback
|
|
272
|
-
self.cf.link_quality_updated.add_callback(self.linkQualitySignal.emit)
|
|
230
|
+
self.cf.link_statistics.link_quality_updated.add_callback(self.linkQualitySignal.emit)
|
|
273
231
|
self.linkQualitySignal.connect(
|
|
274
|
-
lambda percentage: self.linkQualityBar.setValue(percentage))
|
|
275
|
-
|
|
276
|
-
self._selected_interface = None
|
|
277
|
-
self._initial_scan = True
|
|
278
|
-
self._scan()
|
|
232
|
+
lambda percentage: self.linkQualityBar.setValue(int(percentage)))
|
|
279
233
|
|
|
280
234
|
# Parse the log configuration files
|
|
281
235
|
self.logConfigReader = LogConfigReader(self.cf)
|
|
@@ -290,69 +244,33 @@ class MainUI(QtWidgets.QMainWindow, main_window_class):
|
|
|
290
244
|
cfclient.ui.pluginhelper.cf = self.cf
|
|
291
245
|
cfclient.ui.pluginhelper.inputDeviceReader = self.joystickReader
|
|
292
246
|
cfclient.ui.pluginhelper.logConfigReader = self.logConfigReader
|
|
247
|
+
cfclient.ui.pluginhelper.pose_logger = PoseLogger(self.cf)
|
|
248
|
+
cfclient.ui.pluginhelper.connectivity_manager = self._connectivity_manager
|
|
249
|
+
cfclient.ui.pluginhelper.mainUI = self
|
|
293
250
|
|
|
294
251
|
self.logConfigDialogue = LogConfigDialogue(cfclient.ui.pluginhelper)
|
|
295
252
|
self._bootloader_dialog = BootloaderDialog(cfclient.ui.pluginhelper)
|
|
296
253
|
self._cf2config_dialog = Cf2ConfigDialog(cfclient.ui.pluginhelper)
|
|
297
|
-
self._cf1config_dialog = Cf1ConfigDialog(cfclient.ui.pluginhelper)
|
|
298
254
|
self.menuItemBootloader.triggered.connect(self._bootloader_dialog.show)
|
|
299
255
|
self._about_dialog = AboutDialog(cfclient.ui.pluginhelper)
|
|
300
256
|
self.menuItemAbout.triggered.connect(self._about_dialog.show)
|
|
301
257
|
self._menu_cf2_config.triggered.connect(self._cf2config_dialog.show)
|
|
302
|
-
self._menu_cf1_config.triggered.connect(self._cf1config_dialog.show)
|
|
303
|
-
|
|
304
|
-
# Load and connect tabs
|
|
305
|
-
self.tabsMenuItem = QMenu("Tabs", self.menuView, enabled=True)
|
|
306
|
-
self.menuView.addMenu(self.tabsMenuItem)
|
|
307
|
-
|
|
308
|
-
# self.tabsMenuItem.setMenu(QtWidgets.QMenu())
|
|
309
|
-
tabItems = {}
|
|
310
|
-
self.loadedTabs = []
|
|
311
|
-
for tabClass in cfclient.ui.tabs.available:
|
|
312
|
-
tab = tabClass(self.tabs, cfclient.ui.pluginhelper)
|
|
313
|
-
item = QtWidgets.QAction(tab.getMenuName(), self, checkable=True)
|
|
314
|
-
item.toggled.connect(tab.toggleVisibility)
|
|
315
|
-
self.tabsMenuItem.addAction(item)
|
|
316
|
-
tabItems[tab.getTabName()] = item
|
|
317
|
-
self.loadedTabs.append(tab)
|
|
318
|
-
if not tab.enabled:
|
|
319
|
-
item.setEnabled(False)
|
|
320
|
-
|
|
321
|
-
# First instantiate all tabs and then open them in the correct order
|
|
322
|
-
try:
|
|
323
|
-
for tName in Config().get("open_tabs").split(","):
|
|
324
|
-
t = tabItems[tName]
|
|
325
|
-
if (t is not None and t.isEnabled()):
|
|
326
|
-
# Toggle though menu so it's also marked as open there
|
|
327
|
-
t.toggle()
|
|
328
|
-
except Exception as e:
|
|
329
|
-
logger.warning("Exception while opening tabs [{}]".format(e))
|
|
330
|
-
|
|
331
|
-
# Loading toolboxes (A bit of magic for a lot of automatic)
|
|
332
|
-
self.toolboxesMenuItem = QMenu("Toolboxes", self.menuView,
|
|
333
|
-
enabled=True)
|
|
334
|
-
self.menuView.addMenu(self.toolboxesMenuItem)
|
|
335
258
|
|
|
336
|
-
self.
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
dockToolbox.setWidget(toolbox)
|
|
341
|
-
self.toolboxes += [dockToolbox, ]
|
|
259
|
+
self._connectivity_manager.set_address(self.address.value())
|
|
260
|
+
|
|
261
|
+
self._initial_scan = True
|
|
262
|
+
self._scan(self._connectivity_manager.get_address())
|
|
342
263
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
item.setCheckable(True)
|
|
346
|
-
item.triggered.connect(self.toggleToolbox)
|
|
347
|
-
self.toolboxesMenuItem.addAction(item)
|
|
264
|
+
self.tabs_menu_item = QMenu("Tabs", self.menuView, enabled=True)
|
|
265
|
+
self.menuView.addMenu(self.tabs_menu_item)
|
|
348
266
|
|
|
349
|
-
|
|
267
|
+
self.toolboxes_menu_item = QMenu("Toolboxes", self.menuView, enabled=True)
|
|
268
|
+
self.menuView.addMenu(self.toolboxes_menu_item)
|
|
350
269
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
dockToolbox.menuItem = item
|
|
270
|
+
self.loaded_tab_toolboxes = self.create_tab_toolboxes(self.tabs_menu_item,
|
|
271
|
+
self.toolboxes_menu_item,
|
|
272
|
+
self.tab_widget)
|
|
273
|
+
self.read_tab_toolbox_config(self.loaded_tab_toolboxes)
|
|
356
274
|
|
|
357
275
|
# References to all the device sub-menus in the "Input device" menu
|
|
358
276
|
self._all_role_menus = ()
|
|
@@ -363,7 +281,8 @@ class MainUI(QtWidgets.QMainWindow, main_window_class):
|
|
|
363
281
|
self._all_mux_nodes = ()
|
|
364
282
|
|
|
365
283
|
# Check which Input muxes are available
|
|
366
|
-
self._mux_group = QActionGroup(self._menu_inputdevice
|
|
284
|
+
self._mux_group = QActionGroup(self._menu_inputdevice)
|
|
285
|
+
self._mux_group.setExclusive(True)
|
|
367
286
|
for m in self.joystickReader.available_mux():
|
|
368
287
|
node = QAction(m.name,
|
|
369
288
|
self._menu_inputdevice,
|
|
@@ -386,18 +305,110 @@ class MainUI(QtWidgets.QMainWindow, main_window_class):
|
|
|
386
305
|
|
|
387
306
|
self._mapping_support = True
|
|
388
307
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
308
|
+
# Add checkbuttons for theme-selection.
|
|
309
|
+
self._theme_group = QActionGroup(self.menuThemes)
|
|
310
|
+
self._theme_group.setExclusive(True)
|
|
311
|
+
self._theme_checkboxes = []
|
|
312
|
+
for theme in UiUtils.THEMES:
|
|
313
|
+
node = QAction(theme, self.menuThemes, checkable=True)
|
|
314
|
+
node.setObjectName(theme)
|
|
315
|
+
node.toggled.connect(self._theme_selected)
|
|
316
|
+
self._theme_checkboxes.append(node)
|
|
317
|
+
self._theme_group.addAction(node)
|
|
318
|
+
self.menuThemes.addAction(node)
|
|
319
|
+
|
|
320
|
+
# We only want to warn about USB permission once
|
|
321
|
+
self._permission_warned = False
|
|
322
|
+
|
|
323
|
+
def create_tab_toolboxes(self, tabs_menu_item, toolboxes_menu_item, tab_widget):
|
|
324
|
+
loaded_tab_toolboxes = {}
|
|
325
|
+
|
|
326
|
+
for tab_class in cfclient.ui.tabs.available:
|
|
327
|
+
tab_toolbox = tab_class(cfclient.ui.pluginhelper)
|
|
328
|
+
loaded_tab_toolboxes[tab_toolbox.get_tab_toolbox_name()] = tab_toolbox
|
|
329
|
+
|
|
330
|
+
# Set reference for plot-tab.
|
|
331
|
+
if isinstance(tab_toolbox, cfclient.ui.tabs.PlotTab):
|
|
332
|
+
cfclient.ui.pluginhelper.plotTab = tab_toolbox
|
|
333
|
+
|
|
334
|
+
# Add to tabs menu
|
|
335
|
+
tab_action_item = QAction(tab_toolbox.get_tab_toolbox_name())
|
|
336
|
+
tab_action_item.setCheckable(True)
|
|
337
|
+
tab_action_item.triggered.connect(self.toggle_tab_visibility)
|
|
338
|
+
tab_action_item.tab_toolbox = tab_toolbox
|
|
339
|
+
tab_toolbox.tab_action_item = tab_action_item
|
|
340
|
+
|
|
341
|
+
tabs_menu_item.addAction(tab_action_item)
|
|
342
|
+
|
|
343
|
+
# Add to toolbox menu
|
|
344
|
+
toolbox_action_item = QAction(tab_toolbox.get_tab_toolbox_name())
|
|
345
|
+
toolbox_action_item.setCheckable(True)
|
|
346
|
+
toolbox_action_item.triggered.connect(self.toggle_toolbox_visibility)
|
|
347
|
+
toolbox_action_item.tab_toolbox = tab_toolbox
|
|
348
|
+
tab_toolbox.toolbox_action_item = toolbox_action_item
|
|
349
|
+
tab_toolbox.dock_widget.closed.connect(lambda: self.toggle_toolbox_visibility(False))
|
|
350
|
+
tab_toolbox.dock_widget.dockLocationChanged.connect(lambda area: self.set_preferred_dock_area(area))
|
|
351
|
+
|
|
352
|
+
toolboxes_menu_item.addAction(toolbox_action_item)
|
|
353
|
+
|
|
354
|
+
return loaded_tab_toolboxes
|
|
355
|
+
|
|
356
|
+
def read_tab_toolbox_config(self, loaded_tab_toolboxes):
|
|
357
|
+
# Add tabs in the correct order
|
|
358
|
+
for name in TabToolbox.read_open_tab_config():
|
|
359
|
+
if name in loaded_tab_toolboxes.keys():
|
|
360
|
+
self._tab_toolbox_show_as_tab(loaded_tab_toolboxes[name])
|
|
361
|
+
|
|
362
|
+
for name in TabToolbox.read_open_toolbox_config():
|
|
363
|
+
if name in loaded_tab_toolboxes.keys():
|
|
364
|
+
self._tab_toolbox_show_as_toolbox(loaded_tab_toolboxes[name])
|
|
365
|
+
|
|
366
|
+
def _set_address(self):
|
|
367
|
+
address = 0xE7E7E7E7E7
|
|
368
|
+
try:
|
|
369
|
+
link_uri = Config().get("link_uri")
|
|
370
|
+
if link_uri.startswith("radio://"):
|
|
371
|
+
if len(link_uri) > 0:
|
|
372
|
+
parts = link_uri.split('/')
|
|
373
|
+
# The uri might not contain an address
|
|
374
|
+
if len(parts) == 6:
|
|
375
|
+
address = int(parts[-1], 16)
|
|
376
|
+
except Exception as err:
|
|
377
|
+
logger.warn('failed to parse address from config: %s' % str(err))
|
|
378
|
+
finally:
|
|
379
|
+
self.address.setValue(address)
|
|
380
|
+
|
|
381
|
+
def _theme_selected(self, *args):
|
|
382
|
+
""" Callback when a theme is selected. """
|
|
383
|
+
for checkbox in self._theme_checkboxes:
|
|
384
|
+
if checkbox.isChecked():
|
|
385
|
+
theme = checkbox.objectName()
|
|
386
|
+
app = QtWidgets.QApplication.instance()
|
|
387
|
+
app.setStyleSheet(UiUtils.select_theme(theme))
|
|
388
|
+
Config().set('theme', theme)
|
|
389
|
+
|
|
390
|
+
def _check_theme(self, theme_name):
|
|
391
|
+
# Check the default theme.
|
|
392
|
+
for theme in self._theme_checkboxes:
|
|
393
|
+
if theme.objectName() == theme_name:
|
|
394
|
+
theme.setChecked(True)
|
|
395
|
+
self._theme_selected(True)
|
|
396
|
+
|
|
397
|
+
def set_default_theme(self):
|
|
398
|
+
try:
|
|
399
|
+
theme = Config().get('theme')
|
|
400
|
+
except KeyError:
|
|
401
|
+
theme = 'Default'
|
|
402
|
+
self._check_theme(theme)
|
|
395
403
|
|
|
396
|
-
def
|
|
397
|
-
|
|
404
|
+
def disable_input(self, disable):
|
|
405
|
+
"""
|
|
406
|
+
Disable the gamepad input to be able to send setpoint from a tab
|
|
407
|
+
"""
|
|
408
|
+
self._disable_input = disable
|
|
398
409
|
|
|
399
|
-
|
|
400
|
-
self.
|
|
410
|
+
def foundInterfaces(self, interfaces):
|
|
411
|
+
selected_interface = self._connectivity_manager.get_interface()
|
|
401
412
|
|
|
402
413
|
formatted_interfaces = []
|
|
403
414
|
for i in interfaces:
|
|
@@ -406,7 +417,6 @@ class MainUI(QtWidgets.QMainWindow, main_window_class):
|
|
|
406
417
|
else:
|
|
407
418
|
interface = i[0]
|
|
408
419
|
formatted_interfaces.append(interface)
|
|
409
|
-
self.interfaceCombo.addItems(formatted_interfaces)
|
|
410
420
|
|
|
411
421
|
if self._initial_scan:
|
|
412
422
|
self._initial_scan = False
|
|
@@ -425,14 +435,14 @@ class MainUI(QtWidgets.QMainWindow, main_window_class):
|
|
|
425
435
|
if len(interfaces) == 1 and selected_interface is None:
|
|
426
436
|
selected_interface = interfaces[0][0]
|
|
427
437
|
|
|
428
|
-
newIndex =
|
|
438
|
+
newIndex = None
|
|
429
439
|
if selected_interface is not None:
|
|
430
440
|
try:
|
|
431
|
-
newIndex = formatted_interfaces.index(selected_interface)
|
|
441
|
+
newIndex = formatted_interfaces.index(selected_interface)
|
|
432
442
|
except ValueError:
|
|
433
443
|
pass
|
|
434
444
|
|
|
435
|
-
self.
|
|
445
|
+
self._connectivity_manager.set_interfaces(formatted_interfaces, newIndex)
|
|
436
446
|
|
|
437
447
|
self.uiState = UIState.DISCONNECTED
|
|
438
448
|
self._update_ui_state()
|
|
@@ -440,75 +450,108 @@ class MainUI(QtWidgets.QMainWindow, main_window_class):
|
|
|
440
450
|
def _update_ui_state(self):
|
|
441
451
|
if self.uiState == UIState.DISCONNECTED:
|
|
442
452
|
self.setWindowTitle("Not connected")
|
|
443
|
-
canConnect = self.
|
|
453
|
+
canConnect = self._connectivity_manager.get_interface() is not None
|
|
444
454
|
self.menuItemConnect.setText("Connect to Crazyflie")
|
|
445
455
|
self.menuItemConnect.setEnabled(canConnect)
|
|
446
|
-
self.
|
|
447
|
-
self.connectButton.setToolTip(
|
|
448
|
-
"Connect to the Crazyflie on the selected interface")
|
|
449
|
-
self.connectButton.setEnabled(canConnect)
|
|
450
|
-
self.scanButton.setText("Scan")
|
|
451
|
-
self.scanButton.setEnabled(True)
|
|
452
|
-
self.address.setEnabled(True)
|
|
456
|
+
self._connectivity_manager.set_state(ConnectivityManager.UIState.DISCONNECTED)
|
|
453
457
|
self.batteryBar.setValue(3000)
|
|
454
458
|
self._menu_cf2_config.setEnabled(False)
|
|
455
|
-
self._menu_cf1_config.setEnabled(True)
|
|
456
459
|
self.linkQualityBar.setValue(0)
|
|
457
|
-
self.menuItemBootloader.setEnabled(True)
|
|
458
460
|
self.logConfigAction.setEnabled(False)
|
|
459
|
-
self.
|
|
461
|
+
self.esButton.setStyleSheet("")
|
|
462
|
+
self.esButton.setEnabled(False)
|
|
460
463
|
elif self.uiState == UIState.CONNECTED:
|
|
461
|
-
s = "Connected on %s" % self.
|
|
464
|
+
s = "Connected on %s" % self._connectivity_manager.get_interface()
|
|
462
465
|
self.setWindowTitle(s)
|
|
463
466
|
self.menuItemConnect.setText("Disconnect")
|
|
464
467
|
self.menuItemConnect.setEnabled(True)
|
|
465
|
-
self.
|
|
466
|
-
self.connectButton.setToolTip("Disconnect from the Crazyflie")
|
|
467
|
-
self.scanButton.setEnabled(False)
|
|
468
|
+
self._connectivity_manager.set_state(ConnectivityManager.UIState.CONNECTED)
|
|
468
469
|
self.logConfigAction.setEnabled(True)
|
|
470
|
+
self.esButton.setEnabled(True)
|
|
471
|
+
self.esButton.setStyleSheet("background-color: red")
|
|
469
472
|
# Find out if there's an I2C EEPROM, otherwise don't show the
|
|
470
473
|
# dialog.
|
|
471
474
|
if len(self.cf.mem.get_mems(MemoryElement.TYPE_I2C)) > 0:
|
|
472
475
|
self._menu_cf2_config.setEnabled(True)
|
|
473
|
-
self._menu_cf1_config.setEnabled(False)
|
|
474
476
|
elif self.uiState == UIState.CONNECTING:
|
|
475
|
-
s = "Connecting to {} ...".format(self.
|
|
477
|
+
s = "Connecting to {} ...".format(self._connectivity_manager.get_interface())
|
|
476
478
|
self.setWindowTitle(s)
|
|
477
479
|
self.menuItemConnect.setText("Cancel")
|
|
478
480
|
self.menuItemConnect.setEnabled(True)
|
|
479
|
-
self.
|
|
480
|
-
self.connectButton.setToolTip("Cancel connecting to the Crazyflie")
|
|
481
|
-
self.scanButton.setEnabled(False)
|
|
482
|
-
self.address.setEnabled(False)
|
|
483
|
-
self.menuItemBootloader.setEnabled(False)
|
|
484
|
-
self.interfaceCombo.setEnabled(False)
|
|
481
|
+
self._connectivity_manager.set_state(ConnectivityManager.UIState.CONNECTING)
|
|
485
482
|
elif self.uiState == UIState.SCANNING:
|
|
486
483
|
self.setWindowTitle("Scanning ...")
|
|
487
|
-
self.connectButton.setText("Connect")
|
|
488
484
|
self.menuItemConnect.setEnabled(False)
|
|
489
|
-
self.
|
|
490
|
-
self.connectButton.setEnabled(False)
|
|
491
|
-
self.scanButton.setText("Scanning...")
|
|
492
|
-
self.scanButton.setEnabled(False)
|
|
493
|
-
self.address.setEnabled(False)
|
|
494
|
-
self.menuItemBootloader.setEnabled(False)
|
|
495
|
-
self.interfaceCombo.setEnabled(False)
|
|
485
|
+
self._connectivity_manager.set_state(ConnectivityManager.UIState.SCANNING)
|
|
496
486
|
|
|
497
487
|
@pyqtSlot(bool)
|
|
498
|
-
def
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
if
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
488
|
+
def toggle_tab_visibility(self, checked):
|
|
489
|
+
tab_action_item = self.sender()
|
|
490
|
+
tab_toolbox = tab_action_item.tab_toolbox
|
|
491
|
+
|
|
492
|
+
if checked:
|
|
493
|
+
self._tab_toolbox_show_as_tab(tab_toolbox)
|
|
494
|
+
else:
|
|
495
|
+
self._tab_toolbox_hide(tab_toolbox)
|
|
496
|
+
|
|
497
|
+
@pyqtSlot(bool)
|
|
498
|
+
def toggle_toolbox_visibility(self, checked):
|
|
499
|
+
toolbox_action_item = self.sender()
|
|
500
|
+
tab_toolbox = toolbox_action_item.tab_toolbox
|
|
501
|
+
|
|
502
|
+
if checked:
|
|
503
|
+
self._tab_toolbox_show_as_toolbox(tab_toolbox)
|
|
504
|
+
else:
|
|
505
|
+
self._tab_toolbox_hide(tab_toolbox)
|
|
506
|
+
|
|
507
|
+
def _tab_toolbox_show_as_tab(self, tab_toolbox):
|
|
508
|
+
if tab_toolbox.get_display_state() == TabToolbox.DS_TOOLBOX:
|
|
509
|
+
dock_widget = tab_toolbox.dock_widget
|
|
510
|
+
self.removeDockWidget(dock_widget)
|
|
511
|
+
dock_widget.hide()
|
|
512
|
+
|
|
513
|
+
if tab_toolbox.get_display_state() != TabToolbox.DS_TAB:
|
|
514
|
+
tab_toolbox_name = tab_toolbox.get_tab_toolbox_name()
|
|
515
|
+
self.tab_widget.addTab(tab_toolbox, tab_toolbox_name)
|
|
516
|
+
|
|
517
|
+
tab_toolbox.tab_action_item.setChecked(True)
|
|
518
|
+
tab_toolbox.toolbox_action_item.setChecked(False)
|
|
519
|
+
tab_toolbox.set_display_state(TabToolbox.DS_TAB)
|
|
520
|
+
|
|
521
|
+
def _tab_toolbox_show_as_toolbox(self, tab_toolbox):
|
|
522
|
+
dock_widget = tab_toolbox.dock_widget
|
|
523
|
+
|
|
524
|
+
if tab_toolbox.get_display_state() == TabToolbox.DS_TAB:
|
|
525
|
+
self.tab_widget.removeTab(self.tab_widget.indexOf(tab_toolbox))
|
|
526
|
+
|
|
527
|
+
if tab_toolbox.get_display_state() != TabToolbox.DS_TOOLBOX:
|
|
528
|
+
self.addDockWidget(tab_toolbox.preferred_dock_area(), dock_widget)
|
|
529
|
+
dock_widget.setWidget(tab_toolbox)
|
|
530
|
+
dock_widget.show()
|
|
531
|
+
|
|
532
|
+
tab_toolbox.tab_action_item.setChecked(False)
|
|
533
|
+
tab_toolbox.toolbox_action_item.setChecked(True)
|
|
534
|
+
tab_toolbox.set_display_state(TabToolbox.DS_TOOLBOX)
|
|
535
|
+
|
|
536
|
+
def _tab_toolbox_hide(self, tab_toolbox):
|
|
537
|
+
dock_widget = tab_toolbox.dock_widget
|
|
538
|
+
|
|
539
|
+
if tab_toolbox.get_display_state() == TabToolbox.DS_TAB:
|
|
540
|
+
self.tab_widget.removeTab(self.tab_widget.indexOf(tab_toolbox))
|
|
541
|
+
elif tab_toolbox.get_display_state() == TabToolbox.DS_TOOLBOX:
|
|
542
|
+
self.removeDockWidget(dock_widget)
|
|
543
|
+
dock_widget.hide()
|
|
544
|
+
tab_toolbox.toolbox_action_item.setChecked(False)
|
|
545
|
+
|
|
546
|
+
tab_toolbox.tab_action_item.setChecked(False)
|
|
547
|
+
tab_toolbox.toolbox_action_item.setChecked(False)
|
|
548
|
+
tab_toolbox.set_display_state(TabToolbox.DS_HIDDEN)
|
|
549
|
+
|
|
550
|
+
@pyqtSlot(Qt.DockWidgetArea)
|
|
551
|
+
def set_preferred_dock_area(self, area):
|
|
552
|
+
dock_widget = self.sender()
|
|
553
|
+
tab_toolbox = dock_widget.tab_toolbox
|
|
554
|
+
tab_toolbox.set_preferred_dock_area(area)
|
|
512
555
|
|
|
513
556
|
def _rescan_devices(self):
|
|
514
557
|
self._statusbar_label.setText("No inputdevice connected!")
|
|
@@ -526,32 +569,35 @@ class MainUI(QtWidgets.QMainWindow, main_window_class):
|
|
|
526
569
|
self.inputConfig = InputConfigDialogue(self.joystickReader)
|
|
527
570
|
self.inputConfig.show()
|
|
528
571
|
|
|
529
|
-
def _auto_reconnect_changed(self, checked):
|
|
530
|
-
self._auto_reconnect_enabled = checked
|
|
531
|
-
Config().set("auto_reconnect", checked)
|
|
532
|
-
logger.info("Auto reconnect enabled: {}".format(checked))
|
|
533
|
-
|
|
534
572
|
def _show_connect_dialog(self):
|
|
535
573
|
self.logConfigDialogue.show()
|
|
536
574
|
|
|
575
|
+
def _emergency_stop(self):
|
|
576
|
+
# send disarming command
|
|
577
|
+
if (self.uiState == UIState.CONNECTED):
|
|
578
|
+
# Send both emergency stop and disarm
|
|
579
|
+
# TODO krri Disarm?
|
|
580
|
+
self.cf.loc.send_emergency_stop()
|
|
581
|
+
|
|
537
582
|
def _update_battery(self, timestamp, data, logconf):
|
|
538
583
|
self.batteryBar.setValue(int(data["pm.vbat"] * 1000))
|
|
539
584
|
|
|
540
|
-
color = COLOR_BLUE
|
|
585
|
+
color = UiUtils.COLOR_BLUE
|
|
541
586
|
# TODO firmware reports fully-charged state as 'Battery',
|
|
542
587
|
# rather than 'Charged'
|
|
543
588
|
if data["pm.state"] in [BatteryStates.CHARGING, BatteryStates.CHARGED]:
|
|
544
|
-
color = COLOR_GREEN
|
|
589
|
+
color = UiUtils.COLOR_GREEN
|
|
545
590
|
elif data["pm.state"] == BatteryStates.LOW_POWER:
|
|
546
|
-
color = COLOR_RED
|
|
591
|
+
color = UiUtils.COLOR_RED
|
|
547
592
|
|
|
548
|
-
self.batteryBar.setStyleSheet(progressbar_stylesheet(color))
|
|
593
|
+
self.batteryBar.setStyleSheet(UiUtils.progressbar_stylesheet(color))
|
|
594
|
+
self._aff_volts.setText(("%.3f" % data["pm.vbat"]))
|
|
549
595
|
|
|
550
596
|
def _connected(self):
|
|
551
597
|
self.uiState = UIState.CONNECTED
|
|
552
598
|
self._update_ui_state()
|
|
553
599
|
|
|
554
|
-
Config().set("link_uri", str(self.
|
|
600
|
+
Config().set("link_uri", str(self._connectivity_manager.get_interface()))
|
|
555
601
|
|
|
556
602
|
lg = LogConfig("Battery", 1000)
|
|
557
603
|
lg.add_variable("pm.vbat", "float")
|
|
@@ -585,30 +631,24 @@ class MainUI(QtWidgets.QMainWindow, main_window_class):
|
|
|
585
631
|
msg))
|
|
586
632
|
|
|
587
633
|
def _connection_lost(self, linkURI, msg):
|
|
588
|
-
if
|
|
589
|
-
if self.isActiveWindow():
|
|
590
|
-
warningCaption = "Communication failure"
|
|
591
|
-
error = "Connection lost to {}: {}".format(linkURI, msg)
|
|
592
|
-
QMessageBox.critical(self, warningCaption, error)
|
|
593
|
-
self.uiState = UIState.DISCONNECTED
|
|
594
|
-
self._update_ui_state()
|
|
595
|
-
else:
|
|
596
|
-
self._connect()
|
|
597
|
-
|
|
598
|
-
def _connection_failed(self, linkURI, error):
|
|
599
|
-
if not self._auto_reconnect_enabled:
|
|
600
|
-
msg = "Failed to connect on {}: {}".format(linkURI, error)
|
|
634
|
+
if self.isActiveWindow():
|
|
601
635
|
warningCaption = "Communication failure"
|
|
602
|
-
|
|
636
|
+
error = "Connection lost to {}: {}".format(linkURI, msg)
|
|
637
|
+
QMessageBox.critical(self, warningCaption, error)
|
|
603
638
|
self.uiState = UIState.DISCONNECTED
|
|
604
639
|
self._update_ui_state()
|
|
605
|
-
|
|
606
|
-
|
|
640
|
+
|
|
641
|
+
def _connection_failed(self, linkURI, error):
|
|
642
|
+
msg = "Failed to connect on {}: {}".format(linkURI, error)
|
|
643
|
+
warningCaption = "Communication failure"
|
|
644
|
+
QMessageBox.critical(self, warningCaption, msg)
|
|
645
|
+
self.uiState = UIState.DISCONNECTED
|
|
646
|
+
self._update_ui_state()
|
|
607
647
|
|
|
608
648
|
def closeEvent(self, event):
|
|
609
|
-
self.hide()
|
|
610
|
-
self.cf.close_link()
|
|
611
649
|
Config().save_file()
|
|
650
|
+
self.cf.close_link()
|
|
651
|
+
self.hide()
|
|
612
652
|
|
|
613
653
|
def resizeEvent(self, event):
|
|
614
654
|
Config().set("window_size", [event.size().width(),
|
|
@@ -622,12 +662,40 @@ class MainUI(QtWidgets.QMainWindow, main_window_class):
|
|
|
622
662
|
self.uiState = UIState.DISCONNECTED
|
|
623
663
|
self._update_ui_state()
|
|
624
664
|
else:
|
|
625
|
-
self.cf.open_link(self.
|
|
665
|
+
self.cf.open_link(self._connectivity_manager.get_interface())
|
|
626
666
|
|
|
627
|
-
def _scan(self):
|
|
667
|
+
def _scan(self, address):
|
|
628
668
|
self.uiState = UIState.SCANNING
|
|
629
669
|
self._update_ui_state()
|
|
630
|
-
self.scanner.scanSignal.emit(
|
|
670
|
+
self.scanner.scanSignal.emit(address)
|
|
671
|
+
|
|
672
|
+
def _scan_from_button(self, address):
|
|
673
|
+
#
|
|
674
|
+
# Below we check if we can open the Crazyradio device.
|
|
675
|
+
# If it is there, but we have no permissions we inform the user, once,
|
|
676
|
+
# about how to install the udev rules.
|
|
677
|
+
#
|
|
678
|
+
if not self._permission_warned:
|
|
679
|
+
try:
|
|
680
|
+
radio = cflib.crtp.radiodriver.RadioManager.open(0)
|
|
681
|
+
radio.close()
|
|
682
|
+
except usb.core.USBError as e:
|
|
683
|
+
if e.errno == 13: # Permission denied
|
|
684
|
+
link = "<a href='https://www.bitcraze.io/documentation/repository/crazyflie-lib-python/master/installation/usb_permissions/'>Install USB Permissions</a>" # noqa
|
|
685
|
+
msg = QMessageBox()
|
|
686
|
+
msg.setIcon(QMessageBox.Icon.Information)
|
|
687
|
+
msg.setTextFormat(Qt.TextFormat.RichText)
|
|
688
|
+
msg.setText("Could not access Crazyradio")
|
|
689
|
+
msg.setInformativeText(link)
|
|
690
|
+
msg.setWindowTitle("Crazyradio permissions")
|
|
691
|
+
msg.exec()
|
|
692
|
+
self._permission_warned = True
|
|
693
|
+
except Exception as e:
|
|
694
|
+
# For other Crazyradio exceptions (for instance if it's not attached)
|
|
695
|
+
# ignore and keep scanning other link drivers.
|
|
696
|
+
logger.warning(e)
|
|
697
|
+
|
|
698
|
+
self._scan(address)
|
|
631
699
|
|
|
632
700
|
def _display_input_device_error(self, error):
|
|
633
701
|
self.cf.close_link()
|
|
@@ -659,7 +727,7 @@ class MainUI(QtWidgets.QMainWindow, main_window_class):
|
|
|
659
727
|
def _get_dev_status(self, device):
|
|
660
728
|
msg = "{}".format(device.name)
|
|
661
729
|
if device.supports_mapping:
|
|
662
|
-
map_name = "
|
|
730
|
+
map_name = "No input mapping"
|
|
663
731
|
if device.input_map:
|
|
664
732
|
map_name = device.input_map_name
|
|
665
733
|
msg += " ({})".format(map_name)
|
|
@@ -725,8 +793,8 @@ class MainUI(QtWidgets.QMainWindow, main_window_class):
|
|
|
725
793
|
|
|
726
794
|
def _inputconfig_selected(self, checked):
|
|
727
795
|
"""Called when a new configuration has been selected from the menu. The
|
|
728
|
-
data in the menu object is a
|
|
729
|
-
menu. This contains a
|
|
796
|
+
data in the menu object is a reference to the device QAction in parent
|
|
797
|
+
menu. This contains a reference to the raw device."""
|
|
730
798
|
if not checked:
|
|
731
799
|
return
|
|
732
800
|
|
|
@@ -740,7 +808,8 @@ class MainUI(QtWidgets.QMainWindow, main_window_class):
|
|
|
740
808
|
for menu in self._all_role_menus:
|
|
741
809
|
role_menu = menu["rolemenu"]
|
|
742
810
|
mux_menu = menu["muxmenu"]
|
|
743
|
-
dev_group = QActionGroup(role_menu
|
|
811
|
+
dev_group = QActionGroup(role_menu)
|
|
812
|
+
dev_group.setExclusive(True)
|
|
744
813
|
for d in devs:
|
|
745
814
|
dev_node = QAction(d.name, role_menu, checkable=True,
|
|
746
815
|
enabled=True)
|
|
@@ -751,7 +820,8 @@ class MainUI(QtWidgets.QMainWindow, main_window_class):
|
|
|
751
820
|
map_node = None
|
|
752
821
|
if d.supports_mapping:
|
|
753
822
|
map_node = QMenu(" Input map", role_menu, enabled=False)
|
|
754
|
-
map_group = QActionGroup(role_menu
|
|
823
|
+
map_group = QActionGroup(role_menu)
|
|
824
|
+
map_group.setExclusive(True)
|
|
755
825
|
# Connect device node to map node for easy
|
|
756
826
|
# enabling/disabling when selection changes and device
|
|
757
827
|
# to easily enable it
|