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.
Files changed (140) hide show
  1. cfclient/__init__.py +16 -11
  2. cfclient/configs/config.json +4 -3
  3. cfclient/configs/input/Generic_OS_X.json +1 -0
  4. cfclient/configs/input/Joystick.json +1 -0
  5. cfclient/configs/input/PS3_Mode_1.json +1 -0
  6. cfclient/configs/input/PS3_Mode_2.json +1 -0
  7. cfclient/configs/input/PS3_Mode_3.json +1 -0
  8. cfclient/configs/input/PS4_Mode_1.json +1 -0
  9. cfclient/configs/input/PS4_Mode_2.json +1 -0
  10. cfclient/configs/input/PS4_shoulder_btns_yaw.json +1 -0
  11. cfclient/configs/input/xbox360_mode1.json +1 -0
  12. cfclient/configs/log/PID_tuning/Attitude.json +46 -0
  13. cfclient/configs/log/PID_tuning/Attitude_rate.json +46 -0
  14. cfclient/configs/log/PID_tuning/Position.json +46 -0
  15. cfclient/configs/log/PID_tuning/Velocity.json +46 -0
  16. cfclient/configs/log/PID_tuning_components/Pitch.json +22 -0
  17. cfclient/configs/log/PID_tuning_components/Pitch_rate.json +22 -0
  18. cfclient/configs/log/PID_tuning_components/Position_x.json +22 -0
  19. cfclient/configs/log/PID_tuning_components/Position_y.json +22 -0
  20. cfclient/configs/log/PID_tuning_components/Position_z.json +22 -0
  21. cfclient/configs/log/PID_tuning_components/Roll.json +22 -0
  22. cfclient/configs/log/PID_tuning_components/Roll_rate.json +22 -0
  23. cfclient/configs/log/PID_tuning_components/Velocity_x.json +22 -0
  24. cfclient/configs/log/PID_tuning_components/Velocity_y.json +22 -0
  25. cfclient/configs/log/PID_tuning_components/Velocity_z.json +22 -0
  26. cfclient/configs/log/PID_tuning_components/Yaw.json +22 -0
  27. cfclient/configs/log/PID_tuning_components/Yaw_rate.json +22 -0
  28. cfclient/gui.py +44 -9
  29. cfclient/headless.py +3 -12
  30. cfclient/resources/log_param_doc.json +1 -0
  31. cfclient/ui/connectivity_manager.py +198 -0
  32. cfclient/ui/dialogs/about.py +53 -36
  33. cfclient/ui/dialogs/about.ui +23 -3
  34. cfclient/ui/dialogs/anchor_position_dialog.py +252 -0
  35. cfclient/ui/dialogs/anchor_position_dialog.ui +138 -0
  36. cfclient/ui/dialogs/basestation_mode_dialog.py +185 -0
  37. cfclient/ui/dialogs/basestation_mode_dialog.ui +186 -0
  38. cfclient/ui/dialogs/bootloader.py +448 -85
  39. cfclient/ui/dialogs/bootloader.ui +387 -134
  40. cfclient/ui/dialogs/cf2config.py +4 -4
  41. cfclient/ui/dialogs/cf2config.ui +3 -4
  42. cfclient/ui/dialogs/inputconfigdialogue.py +24 -19
  43. cfclient/ui/dialogs/inputconfigdialogue.ui +53 -30
  44. cfclient/ui/dialogs/lighthouse_bs_geometry_dialog.py +220 -0
  45. cfclient/ui/dialogs/lighthouse_bs_geometry_dialog.ui +110 -0
  46. cfclient/ui/dialogs/lighthouse_system_type_dialog.py +93 -0
  47. cfclient/ui/dialogs/lighthouse_system_type_dialog.ui +121 -0
  48. cfclient/ui/dialogs/logconfigdialogue.py +401 -101
  49. cfclient/ui/dialogs/logconfigdialogue.ui +117 -72
  50. cfclient/ui/icons/bl.webp +0 -0
  51. cfclient/ui/icons/bolt.webp +0 -0
  52. cfclient/ui/icons/cf21.webp +0 -0
  53. cfclient/ui/icons/checkmark_black.png +0 -0
  54. cfclient/ui/icons/checkmark_white.png +0 -0
  55. cfclient/ui/icons/create.png +0 -0
  56. cfclient/ui/icons/delete.png +0 -0
  57. cfclient/ui/icons/flapper.webp +0 -0
  58. cfclient/ui/icons/tag.webp +0 -0
  59. cfclient/ui/main.py +328 -258
  60. cfclient/ui/main.ui +184 -80
  61. cfclient/ui/pluginhelper.py +7 -1
  62. cfclient/ui/pose_logger.py +116 -0
  63. cfclient/ui/tab_toolbox.py +208 -0
  64. cfclient/ui/tabs/ColorLEDTab.py +752 -0
  65. cfclient/ui/tabs/ConsoleTab.py +48 -13
  66. cfclient/ui/{toolboxes → tabs}/CrtpSharkToolbox.py +19 -34
  67. cfclient/ui/tabs/ExampleTab.py +9 -16
  68. cfclient/ui/tabs/FlightTab.py +437 -325
  69. cfclient/ui/tabs/GpsTab.py +14 -20
  70. cfclient/ui/tabs/LEDRingTab.py +277 -0
  71. cfclient/ui/tabs/LogBlockDebugTab.py +20 -27
  72. cfclient/ui/tabs/LogBlockTab.py +35 -35
  73. cfclient/ui/tabs/LogClientTab.py +85 -0
  74. cfclient/ui/tabs/LogTab.py +50 -27
  75. cfclient/ui/tabs/ParamTab.py +443 -57
  76. cfclient/ui/tabs/PlotTab.py +23 -25
  77. cfclient/ui/tabs/TuningTab.py +292 -0
  78. cfclient/ui/tabs/__init__.py +12 -2
  79. cfclient/ui/tabs/colorLEDTab.ui +624 -0
  80. cfclient/ui/tabs/consoleTab.ui +46 -0
  81. cfclient/ui/tabs/flightActionContainer.ui +103 -0
  82. cfclient/ui/tabs/flightTab.ui +724 -237
  83. cfclient/ui/tabs/{ledTab.ui → ledRingTab.ui} +63 -46
  84. cfclient/ui/tabs/lighthouse_tab.py +714 -0
  85. cfclient/ui/tabs/lighthouse_tab.ui +430 -0
  86. cfclient/ui/tabs/locopositioning_tab.py +606 -389
  87. cfclient/ui/tabs/locopositioning_tab.ui +370 -253
  88. cfclient/ui/tabs/logClientTab.ui +52 -0
  89. cfclient/ui/tabs/logTab.ui +1 -1
  90. cfclient/ui/tabs/paramTab.ui +204 -3
  91. cfclient/ui/tabs/tuningTab.ui +773 -0
  92. cfclient/ui/widgets/ai.py +37 -39
  93. cfclient/ui/widgets/hexspinbox.py +16 -10
  94. cfclient/ui/widgets/plotter.ui +39 -47
  95. cfclient/ui/widgets/plotwidget.py +57 -22
  96. cfclient/ui/widgets/super_slider.py +112 -0
  97. cfclient/ui/wizards/__init__.py +0 -0
  98. cfclient/ui/wizards/bslh_1.png +0 -0
  99. cfclient/ui/wizards/bslh_2.png +0 -0
  100. cfclient/ui/wizards/bslh_3.png +0 -0
  101. cfclient/ui/wizards/bslh_4.png +0 -0
  102. cfclient/ui/wizards/bslh_5.png +0 -0
  103. cfclient/ui/wizards/lighthouse_geo_bs_estimation_wizard.py +465 -0
  104. cfclient/utils/config_manager.py +5 -4
  105. cfclient/utils/input/__init__.py +77 -19
  106. cfclient/utils/input/inputinterfaces/wiimote.py +2 -2
  107. cfclient/utils/input/inputreaderinterface.py +17 -7
  108. cfclient/utils/input/inputreaders/__init__.py +17 -0
  109. cfclient/utils/logconfigreader.py +245 -25
  110. cfclient/utils/logdatawriter.py +3 -1
  111. cfclient/utils/periodictimer.py +1 -1
  112. cfclient/utils/ui.py +336 -0
  113. cfclient/utils/zmq_led_driver.py +5 -0
  114. cfclient/utils/zmq_param.py +6 -0
  115. cfclient/version.py +34 -1
  116. cfclient-2025.12.1.dist-info/METADATA +70 -0
  117. cfclient-2025.12.1.dist-info/RECORD +152 -0
  118. {cfclient-2017.4.dist-info → cfclient-2025.12.1.dist-info}/WHEEL +1 -1
  119. {cfclient-2017.4.dist-info → cfclient-2025.12.1.dist-info}/entry_points.txt +0 -1
  120. cfclient-2025.12.1.dist-info/licenses/LICENSE.txt +350 -0
  121. {cfclient-2017.4.dist-info → cfclient-2025.12.1.dist-info}/top_level.txt +1 -0
  122. cfconfig/Makefile +51 -0
  123. cfconfig/configblock.py +111 -0
  124. cfloader/__init__.py +41 -55
  125. cfzmq/__init__.py +22 -14
  126. cfclient/ui/dialogs/cf1config.py +0 -265
  127. cfclient/ui/dialogs/cf1config.ui +0 -260
  128. cfclient/ui/tab.py +0 -96
  129. cfclient/ui/tabs/LEDTab.py +0 -169
  130. cfclient/ui/toolboxes/ConsoleToolbox.py +0 -69
  131. cfclient/ui/toolboxes/DebugDriverToolbox.py +0 -107
  132. cfclient/ui/toolboxes/__init__.py +0 -45
  133. cfclient/ui/toolboxes/consoleToolbox.ui +0 -62
  134. cfclient/ui/toolboxes/debugDriverToolbox.ui +0 -86
  135. cfclient-2017.4.dist-info/DESCRIPTION.rst +0 -3
  136. cfclient-2017.4.dist-info/METADATA +0 -22
  137. cfclient-2017.4.dist-info/RECORD +0 -104
  138. cfclient-2017.4.dist-info/metadata.json +0 -1
  139. /cfclient/{icon-256.png → ui/icons/icon-256.png} +0 -0
  140. /cfclient/ui/{toolboxes → tabs}/crtpSharkToolbox.ui +0 -0
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # || ____ _ __
5
+ # +------+ / __ )(_) /_______________ _____ ___
6
+ # | 0xBC | / __ / / __/ ___/ ___/ __ `/_ / / _ \
7
+ # +------+ / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
8
+ # || || /_____/_/\__/\___/_/ \__,_/ /___/\___/
9
+ #
10
+ # Copyright (C) 2021-2023 Bitcraze AB
11
+ #
12
+ # Crazyflie Nano Quadcopter Client
13
+ #
14
+ # This program is free software; you can redistribute it and/or
15
+ # modify it under the terms of the GNU General Public License
16
+ # as published by the Free Software Foundation; either version 2
17
+ # of the License, or (at your option) any later version.
18
+ #
19
+ # This program is distributed in the hope that it will be useful,
20
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
21
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
+ # GNU General Public License for more details.
23
+
24
+ # You should have received a copy of the GNU General Public License
25
+ # along with this program; if not, write to the Free Software
26
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
27
+ # 02110-1301, USA.
28
+ from collections import namedtuple
29
+ from PyQt6.QtCore import pyqtSignal, QObject
30
+
31
+ __author__ = 'Bitcraze AB'
32
+ __all__ = ['ConnectivityManager']
33
+
34
+
35
+ class ConnectivityManager(QObject):
36
+ UiElementsContainer = namedtuple('UiElementContainer', [
37
+ 'interface_combo',
38
+ 'address_spinner',
39
+ 'connect_button',
40
+ 'scan_button'])
41
+
42
+ class UIState:
43
+ DISCONNECTED = 0
44
+ CONNECTING = 1
45
+ CONNECTED = 2
46
+ SCANNING = 3
47
+
48
+ INTERFACE_PROMPT_TEXT = 'Select an interface'
49
+
50
+ connect_button_clicked = pyqtSignal()
51
+ scan_button_clicked = pyqtSignal(object)
52
+ connection_state_changed = pyqtSignal(object)
53
+
54
+ def __init__(self):
55
+ QObject.__init__(self)
56
+ self._ui_elements = []
57
+ self._state = self.UIState.DISCONNECTED
58
+ self._is_enabled = True
59
+
60
+ def register_ui_elements(self, ui_elements):
61
+ self._ui_elements.append(ui_elements)
62
+
63
+ ui_elements.connect_button.clicked.connect(self._connect_button_click_handler)
64
+ ui_elements.scan_button.clicked.connect(self._scan_button_click_handler)
65
+
66
+ ui_elements.address_spinner.valueChanged.connect(self._address_changed_handler)
67
+ ui_elements.address_spinner.editingFinished.connect(self._address_edited_handler)
68
+
69
+ ui_elements.interface_combo.currentIndexChanged.connect(self._interface_combo_current_index_changed_handler)
70
+
71
+ def set_state(self, state):
72
+ if self._state != state:
73
+ self._state = state
74
+ self._update_ui()
75
+
76
+ if self._state == self.UIState.DISCONNECTED:
77
+ self.connection_state_changed.emit(self.UIState.DISCONNECTED)
78
+ elif self._state == self.UIState.CONNECTED:
79
+ self.connection_state_changed.emit(self.UIState.CONNECTED)
80
+ elif self._state == self.UIState.CONNECTING:
81
+ self.connection_state_changed.emit(self.UIState.CONNECTING)
82
+ elif self._state == self.UIState.SCANNING:
83
+ self.connection_state_changed.emit(self.UIState.SCANNING)
84
+
85
+ def set_enable(self, enable):
86
+ if self._is_enabled != enable:
87
+ self._is_enabled = enable
88
+ self._update_ui()
89
+
90
+ def set_address(self, address):
91
+ for ui_elements in self._ui_elements:
92
+ ui_elements.address_spinner.setValue(address)
93
+
94
+ def get_address(self):
95
+ if len(self._ui_elements) > 0:
96
+ return self._ui_elements[0].address_spinner.value()
97
+ else:
98
+ return 0
99
+
100
+ def set_interfaces(self, interface_items, index):
101
+ new_index = 0
102
+ if index is not None:
103
+ new_index = index + 1
104
+
105
+ for ui_elements in self._ui_elements:
106
+ combo = ui_elements.interface_combo
107
+
108
+ combo.clear()
109
+ combo.addItem(self.INTERFACE_PROMPT_TEXT)
110
+ combo.addItems(interface_items)
111
+ combo.setCurrentIndex(new_index)
112
+
113
+ def get_interface(self):
114
+ if len(self._ui_elements) > 0:
115
+ interface = self._ui_elements[0].interface_combo.currentText()
116
+ if interface == self.INTERFACE_PROMPT_TEXT:
117
+ self._selected_interface = None
118
+ else:
119
+ return interface
120
+ else:
121
+ return None
122
+
123
+ def _connect_button_click_handler(self):
124
+ self.connect_button_clicked.emit()
125
+
126
+ def _scan_button_click_handler(self):
127
+ self.scan_button_clicked.emit(self.get_address())
128
+
129
+ def _address_changed_handler(self, value):
130
+ for ui_elements in self._ui_elements:
131
+ if value != ui_elements.address_spinner.value():
132
+ ui_elements.address_spinner.setValue(value)
133
+
134
+ def _address_edited_handler(self):
135
+ # Find out if one of the addresses has changed and what the new value is
136
+ value = 0
137
+ is_changed = False
138
+ for ui_elements in self._ui_elements:
139
+ if ui_elements.address_spinner.is_text_different_from_value():
140
+ value = ui_elements.address_spinner.value()
141
+ is_changed = True
142
+ break
143
+
144
+ # Set the new value
145
+ if is_changed:
146
+ for ui_elements in self._ui_elements:
147
+ if value != ui_elements.address_spinner.value():
148
+ ui_elements.address_spinner.setValue(value)
149
+
150
+ def _interface_combo_current_index_changed_handler(self, interface):
151
+ interface_s = str(interface)
152
+ can_connect = interface != self.INTERFACE_PROMPT_TEXT
153
+ for ui_elements in self._ui_elements:
154
+ combo = ui_elements.interface_combo
155
+ if combo.currentText != interface_s:
156
+ combo.setCurrentText(interface_s)
157
+ ui_elements.connect_button.setEnabled(can_connect)
158
+
159
+ def _update_ui(self):
160
+ if self._is_enabled:
161
+ if self._state == self.UIState.DISCONNECTED:
162
+ can_connect = self.get_interface() is not None
163
+ for ui_elements in self._ui_elements:
164
+ ui_elements.connect_button.setText("Connect")
165
+ ui_elements.connect_button.setToolTip("Connect to the Crazyflie on the selected interface (Ctrl+I)")
166
+ ui_elements.connect_button.setEnabled(can_connect)
167
+ ui_elements.scan_button.setText("Scan")
168
+ ui_elements.scan_button.setEnabled(True)
169
+ ui_elements.address_spinner.setEnabled(True)
170
+ ui_elements.interface_combo.setEnabled(True)
171
+ elif self._state == self.UIState.CONNECTED:
172
+ for ui_elements in self._ui_elements:
173
+ ui_elements.connect_button.setText("Disconnect")
174
+ ui_elements.connect_button.setToolTip("Disconnect from the Crazyflie (Ctrl+I)")
175
+ ui_elements.scan_button.setEnabled(False)
176
+ ui_elements.address_spinner.setEnabled(False)
177
+ ui_elements.interface_combo.setEnabled(False)
178
+ elif self._state == self.UIState.CONNECTING:
179
+ for ui_elements in self._ui_elements:
180
+ ui_elements.connect_button.setText("Cancel")
181
+ ui_elements.connect_button.setToolTip("Cancel connecting to the Crazyflie")
182
+ ui_elements.scan_button.setEnabled(False)
183
+ ui_elements.address_spinner.setEnabled(False)
184
+ ui_elements.interface_combo.setEnabled(False)
185
+ elif self._state == self.UIState.SCANNING:
186
+ for ui_elements in self._ui_elements:
187
+ ui_elements.connect_button.setText("Connect")
188
+ ui_elements.connect_button.setEnabled(False)
189
+ ui_elements.scan_button.setText("Scanning...")
190
+ ui_elements.scan_button.setEnabled(False)
191
+ ui_elements.address_spinner.setEnabled(False)
192
+ ui_elements.interface_combo.setEnabled(False)
193
+ else:
194
+ for ui_elements in self._ui_elements:
195
+ ui_elements.connect_button.setEnabled(False)
196
+ ui_elements.scan_button.setEnabled(False)
197
+ ui_elements.address_spinner.setEnabled(False)
198
+ ui_elements.interface_combo.setEnabled(False)
@@ -7,7 +7,7 @@
7
7
  # +------+ / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
8
8
  # || || /_____/_/\__/\___/_/ \__,_/ /___/\___/
9
9
  #
10
- # Copyright (C) 2011-2013 Bitcraze AB
10
+ # Copyright (C) 2011-2023 Bitcraze AB
11
11
  #
12
12
  # Crazyflie Nano Quadcopter Client
13
13
  #
@@ -31,11 +31,12 @@ import sys
31
31
 
32
32
  import cfclient
33
33
  import cflib.crtp
34
- from PyQt5.QtCore import QT_VERSION_STR
35
- from PyQt5.QtCore import PYQT_VERSION_STR
36
- from PyQt5 import QtWidgets
37
- from PyQt5 import uic
38
- from PyQt5.QtCore import pyqtSignal
34
+ from PyQt6.QtCore import QT_VERSION_STR
35
+ from PyQt6.QtCore import PYQT_VERSION_STR
36
+ from PyQt6 import QtWidgets
37
+ from PyQt6 import uic
38
+ from PyQt6.QtCore import pyqtSignal
39
+ from cflib.crazyflie.mem import MemoryElement
39
40
 
40
41
  __author__ = 'Bitcraze AB'
41
42
  __all__ = ['AboutDialog']
@@ -64,8 +65,13 @@ PyQt: {pyqt_version}<br>
64
65
  <b>Crazyflie</b><br>
65
66
  Connected: {uri}<br>
66
67
  Firmware: {firmware}<br>
68
+ <br>
69
+ <b>Decks found</b><br>
70
+ {decks}
71
+ <br>
67
72
  <b>Sensors found</b><br>
68
73
  {imu_sensors}
74
+ <br>
69
75
  <b>Sensors tests</b><br>
70
76
  {imu_sensor_tests}
71
77
  """
@@ -76,24 +82,12 @@ DEVICE_FORMAT = "{}: ({}) {}<br>"
76
82
  IMU_SENSORS_FORMAT = "{}: {}<br>"
77
83
  SENSOR_TESTS_FORMAT = "{}: {}<br>"
78
84
  FIRMWARE_FORMAT = "{:x}{:x} ({})"
79
-
80
- CREDITS_FORMAT = """
81
- <b>Contributions</b><br>
82
- {contribs}
83
- <br><br>
84
- <b>Used libraries</b><br>
85
- <a href="http://qt-project.org/">QT</a><br>
86
- <a href="http://www.riverbankcomputing.co.uk/software/pyqt/intro">PyQT</a><br>
87
- <a href="http://pysdl2.readthedocs.org">PySDL2</a><br>
88
- <a href="http://www.pyqtgraph.org/">PyQtGraph</a><br>
89
- <a href="http://marble.kde.org/">KDE Marble</a><br>
90
- <a href="http://sourceforge.net/projects/pyusb/">PyUSB</a><br>
91
- <a href="http://www.python.org/">Python</a><br>
92
- """
85
+ DECK_FORMAT = "{}: rev={}, adr={}<br>"
93
86
 
94
87
 
95
88
  class AboutDialog(QtWidgets.QWidget, about_widget_class):
96
89
  _disconnected_signal = pyqtSignal(str)
90
+ _cb_deck_data_updated_signal = pyqtSignal(object)
97
91
 
98
92
  """Crazyflie client About box for debugging and information"""
99
93
 
@@ -107,10 +101,13 @@ class AboutDialog(QtWidgets.QWidget, about_widget_class):
107
101
  self._interface_text = ""
108
102
  self._imu_sensors_text = ""
109
103
  self._imu_sensor_test_text = ""
104
+ self._decks_text = ""
110
105
  self._uri = None
111
106
  self._fw_rev0 = None
112
107
  self._fw_rev1 = None
113
108
  self._fw_modified = None
109
+ self._firmware = None
110
+
114
111
  self._helper = helper
115
112
 
116
113
  helper.cf.param.add_update_callback(
@@ -124,18 +121,7 @@ class AboutDialog(QtWidgets.QWidget, about_widget_class):
124
121
  self._disconnected_signal.connect(self._disconnected)
125
122
  helper.cf.disconnected.add_callback(self._disconnected_signal.emit)
126
123
 
127
- # Open the Credits file and show it in the UI
128
- credits = ""
129
- try:
130
- with open("CREDITS.txt", encoding='utf-8', mode='r') as f:
131
- for line in f:
132
- credits += "{}<br>".format(line)
133
- except IOError:
134
- credits = ""
135
-
136
- self._credits.setHtml(
137
- CREDITS_FORMAT.format(contribs=credits)
138
- )
124
+ self._cb_deck_data_updated_signal.connect(self._deck_data_updated)
139
125
 
140
126
  def showEvent(self, event):
141
127
  """Event when the about box is shown"""
@@ -144,7 +130,6 @@ class AboutDialog(QtWidgets.QWidget, about_widget_class):
144
130
  for key in list(interface_status.keys()):
145
131
  self._interface_text += INTERFACE_FORMAT.format(
146
132
  key, interface_status[key])
147
- firmware = None
148
133
 
149
134
  self._device_text = ""
150
135
  devs = self._helper.inputDeviceReader.available_devices()
@@ -163,10 +148,16 @@ class AboutDialog(QtWidgets.QWidget, about_widget_class):
163
148
  self._input_readers_text = "None<br>"
164
149
 
165
150
  if self._uri:
166
- firmware = FIRMWARE_FORMAT.format(
151
+ self._firmware = FIRMWARE_FORMAT.format(
167
152
  self._fw_rev0,
168
153
  self._fw_rev1,
169
154
  "MODIFIED" if self._fw_modified else "CLEAN")
155
+
156
+ self._request_deck_data_update()
157
+
158
+ self._update_debug_info_view()
159
+
160
+ def _update_debug_info_view(self):
170
161
  self._debug_out.setHtml(
171
162
  DEBUG_INFO_FORMAT.format(
172
163
  version=cfclient.VERSION,
@@ -180,9 +171,10 @@ class AboutDialog(QtWidgets.QWidget, about_widget_class):
180
171
  input_devices=self._device_text,
181
172
  input_readers=self._input_readers_text,
182
173
  uri=self._uri,
183
- firmware=firmware,
174
+ firmware=self._firmware,
184
175
  imu_sensors=self._imu_sensors_text,
185
- imu_sensor_tests=self._imu_sensor_test_text))
176
+ imu_sensor_tests=self._imu_sensor_test_text,
177
+ decks=self._decks_text))
186
178
 
187
179
  def _connected(self, uri):
188
180
  """Callback when Crazyflie is connected"""
@@ -216,7 +208,32 @@ class AboutDialog(QtWidgets.QWidget, about_widget_class):
216
208
  self._interface_text = ""
217
209
  self._imu_sensors_text = ""
218
210
  self._imu_sensor_test_text = ""
211
+ self._decks_text = ""
219
212
  self._uri = None
220
213
  self._fw_rev1 = None
221
214
  self._fw_rev0 = None
222
215
  self._fw_modified = None
216
+ self._firmware = None
217
+
218
+ def _request_deck_data_update(self):
219
+ self._decks_text = ""
220
+ # Query both 1-Wire and DeckCtrl memories for deck information
221
+ mems = self._helper.cf.mem.get_mems(MemoryElement.TYPE_1W)
222
+ mems += self._helper.cf.mem.get_mems(MemoryElement.TYPE_DECKCTRL)
223
+ for mem in mems:
224
+ mem.update(self._cb_deck_data_updated_signal.emit)
225
+
226
+ def _deck_data_updated(self, deck_data):
227
+ name = 'N/A'
228
+ if "Board name" in deck_data.elements:
229
+ name = deck_data.elements["Board name"]
230
+
231
+ rev = 'N/A'
232
+ if "Board revision" in deck_data.elements:
233
+ rev = deck_data.elements["Board revision"]
234
+
235
+ # OWElement has addr attribute, DeckCtrlElement does not
236
+ addr = getattr(deck_data, 'addr', 'N/A')
237
+ self._decks_text += DECK_FORMAT.format(name, rev, addr)
238
+
239
+ self._update_debug_info_view()
@@ -39,14 +39,20 @@
39
39
  <item row="1" column="0">
40
40
  <widget class="QLabel" name="label_2">
41
41
  <property name="text">
42
- <string>Copyright (c) 2011-2017, Bitcraze AB</string>
42
+ <string>Copyright (c) 2011-2023, Bitcraze AB</string>
43
43
  </property>
44
44
  </widget>
45
45
  </item>
46
46
  <item row="2" column="0">
47
47
  <widget class="QLabel" name="label">
48
48
  <property name="text">
49
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;The Crazyflie client is a multi-platform client&lt;br/&gt;for controlling, bootloading and logging the Crazyflie.&lt;br/&gt;For more info visit our homepage.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;http://www.bitcraze.io&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Bitcraze Homepage&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;&lt;br/&gt;&lt;/span&gt;&lt;a href=&quot;http://wiki.bitcraze.io/doc:crazyflie:client:pycfclient:index&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Client documentation on Bitcraze Wiki&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;&lt;br/&gt;&lt;/span&gt;&lt;a href=&quot;http://forum.bitcraze.se&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Bitcraze Forum&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
49
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;The Crazyflie client is a multi-platform client&lt;br/&gt;for controlling, bootloading and logging the Crazyflie.&lt;br/&gt;For more info visit our homepage.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;http://www.bitcraze.io&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Bitcraze Homepage&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;&lt;br/&gt;&lt;/span&gt;&lt;a href=&quot;https://www.bitcraze.io/documentation/repository/crazyflie-clients-python/master/userguides/userguide_client/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Client documentation&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;&lt;br/&gt;&lt;/span&gt;&lt;a href=&quot;http://discussions.bitcraze.io&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Bitcraze Discussions&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
50
+ </property>
51
+ <property name="openExternalLinks">
52
+ <bool>true</bool>
53
+ </property>
54
+ <property name="textInteractionFlags">
55
+ <set>Qt::TextBrowserInteraction</set>
50
56
  </property>
51
57
  </widget>
52
58
  </item>
@@ -84,7 +90,21 @@
84
90
  <item>
85
91
  <layout class="QGridLayout" name="gridLayout_3">
86
92
  <item row="0" column="0">
87
- <widget class="QTextEdit" name="_credits"/>
93
+ <widget class="QTextBrowser" name="_credits">
94
+ <property name="html">
95
+ <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
96
+ &lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
97
+ p, li { white-space: pre-wrap; }
98
+ &lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
99
+ &lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Contributions&lt;/span&gt;&lt;br /&gt;We are very grateful for all the contributions we have received for this project. Below is a link showing all of the users that have contributed to the Crazyflie client. &lt;br /&gt;&lt;br /&gt;Thank you! &lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://github.com/bitcraze/crazyflie-clients-python/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#1b6acb;&quot;&gt;Contributor information on GitHub&lt;/span&gt;&lt;/a&gt; &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
100
+ </property>
101
+ <property name="textInteractionFlags">
102
+ <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
103
+ </property>
104
+ <property name="openExternalLinks">
105
+ <bool>true</bool>
106
+ </property>
107
+ </widget>
88
108
  </item>
89
109
  </layout>
90
110
  </item>
@@ -0,0 +1,252 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # || ____ _ __
4
+ # +------+ / __ )(_) /_______________ _____ ___
5
+ # | 0xBC | / __ / / __/ ___/ ___/ __ `/_ / / _ \
6
+ # +------+ / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
7
+ # || || /_____/_/\__/\___/_/ \__,_/ /___/\___/
8
+ #
9
+ # Copyright (C) 2018-2023 Bitcraze AB
10
+ #
11
+ # This program is free software; you can redistribute it and/or
12
+ # modify it under the terms of the GNU General Public License
13
+ # as published by the Free Software Foundation; either version 2
14
+ # of the License, or (at your option) any later version.
15
+ #
16
+ # This program is distributed in the hope that it will be useful,
17
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
+ # GNU General Public License for more details.
20
+ # You should have received a copy of the GNU General Public License
21
+ # along with this program; if not, write to the Free Software
22
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
23
+ # MA 02110-1301, USA.
24
+
25
+ """
26
+ Dialog box used to configure anchor positions. Used from the LPS tab.
27
+ """
28
+ import logging
29
+
30
+ import cfclient
31
+ from cfclient.utils.logconfigreader import FILE_REGEX_YAML
32
+ from PyQt6 import QtWidgets
33
+ from PyQt6 import uic
34
+ from PyQt6.QtCore import QAbstractTableModel, QVariant, Qt
35
+ from PyQt6.QtGui import QBrush, QColor
36
+ from PyQt6.QtWidgets import QInputDialog, QFileDialog
37
+ import yaml
38
+ import os
39
+
40
+ __author__ = 'Bitcraze AB'
41
+ __all__ = ['AnchorPositionDialog']
42
+
43
+ logger = logging.getLogger(__name__)
44
+
45
+ (anchor_postiong_widget_class, connect_widget_base_class) = (
46
+ uic.loadUiType(
47
+ cfclient.module_path + '/ui/dialogs/anchor_position_dialog.ui')
48
+ )
49
+
50
+
51
+ class AnchorPositionConfigTableModel(QAbstractTableModel):
52
+ def __init__(self, headers, parent=None, *args):
53
+ QAbstractTableModel.__init__(self, parent)
54
+ self._anchor_positions = []
55
+ self._headers = headers
56
+ self._latest_known_anchor_positions = {}
57
+
58
+ self._green_brush = QBrush(QColor(200, 255, 200))
59
+ self._red_brush = QBrush(QColor(255, 200, 200))
60
+
61
+ def rowCount(self, parent=None, *args, **kwargs):
62
+ return len(self._anchor_positions)
63
+
64
+ def columnCount(self, parent=None, *args, **kwargs):
65
+ return len(self._headers)
66
+
67
+ def data(self, index, role=None):
68
+ value = self._anchor_positions[index.row()][index.column()]
69
+ if index.isValid():
70
+ if index.column() == 0:
71
+ if role == Qt.ItemDataRole.CheckStateRole:
72
+ return QVariant(value)
73
+ elif index.column() == 1:
74
+ if role == Qt.ItemDataRole.DisplayRole:
75
+ return QVariant(value)
76
+ else:
77
+ if role == Qt.ItemDataRole.DisplayRole:
78
+ return QVariant('%.2f' % (value))
79
+ elif role == Qt.ItemDataRole.EditRole:
80
+ return QVariant(value)
81
+ elif role == Qt.ItemDataRole.BackgroundRole:
82
+ return self._get_background(index.row(), index.column())
83
+
84
+ return QVariant()
85
+
86
+ def setData(self, index, value, role=Qt.ItemDataRole.EditRole):
87
+ if not index.isValid():
88
+ return False
89
+
90
+ self._anchor_positions[index.row()][index.column()] = value
91
+ return True
92
+
93
+ def headerData(self, col, orientation, role=None):
94
+ if orientation == Qt.Orientation.Horizontal and role == Qt.ItemDataRole.DisplayRole:
95
+ return QVariant(self._headers[col])
96
+ return QVariant()
97
+
98
+ def flags(self, index):
99
+ if not index.isValid():
100
+ return None
101
+
102
+ if index.column() == 0:
103
+ return Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsUserCheckable
104
+ elif index.column() == 1:
105
+ return Qt.ItemFlag.ItemIsEnabled
106
+ else:
107
+ return Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsEditable
108
+
109
+ def add_anchor(self, anchor_id, x=0.0, y=0.0, z=0.0):
110
+ if not self._id_exist(anchor_id):
111
+ self.layoutAboutToBeChanged.emit()
112
+ self._anchor_positions.append([0, anchor_id, x, y, z])
113
+ self._anchor_positions.sort(key=lambda row: row[1])
114
+ self.layoutChanged.emit()
115
+
116
+ def replace_anchors_from_latest_known_positions(self):
117
+ self.replace_anchor_positions(self._latest_known_anchor_positions)
118
+
119
+ def replace_anchor_positions(self, anchor_positions):
120
+ self.layoutAboutToBeChanged.emit()
121
+ self._anchor_positions = []
122
+ for id, position in anchor_positions.items():
123
+ self.add_anchor(id, x=position[0], y=position[1], z=position[2])
124
+ self.layoutChanged.emit()
125
+
126
+ def get_anchor_postions(self):
127
+ result = {}
128
+ for row in self._anchor_positions:
129
+ result[row[1]] = (row[2], row[3], row[4])
130
+ return result
131
+
132
+ def anchor_postions_updated(self, anchor_positions):
133
+ self.layoutAboutToBeChanged.emit()
134
+ self._latest_known_anchor_positions = anchor_positions
135
+ self.layoutChanged.emit()
136
+
137
+ def remove_selected_anchors(self):
138
+ self.layoutAboutToBeChanged.emit()
139
+ self._anchor_positions = list(filter(
140
+ lambda row: row[0] == 0, self._anchor_positions))
141
+ self.layoutChanged.emit()
142
+
143
+ def _id_exist(self, anchor_id):
144
+ for anchor in self._anchor_positions:
145
+ if anchor[1] == anchor_id:
146
+ return True
147
+ return False
148
+
149
+ def _get_background(self, row, col):
150
+ id = self._anchor_positions[row][1]
151
+ if id in self._latest_known_anchor_positions:
152
+ current_value = self._anchor_positions[row][col]
153
+ latest_value = self._latest_known_anchor_positions[id][col - 2]
154
+
155
+ if abs(current_value - latest_value) < 0.005:
156
+ return self._green_brush
157
+ else:
158
+ return self._red_brush
159
+
160
+ return QVariant()
161
+
162
+
163
+ class AnchorPositionDialog(QtWidgets.QWidget, anchor_postiong_widget_class):
164
+
165
+ def __init__(self, lps_tab, helper):
166
+ super(AnchorPositionDialog, self).__init__()
167
+ self.setupUi(self)
168
+
169
+ self._lps_tab = lps_tab
170
+ self._helper = helper
171
+
172
+ self._headers = ['', 'id', 'x', 'y', 'z']
173
+ self._data_model = AnchorPositionConfigTableModel(self._headers, self)
174
+ self._table_view.setModel(self._data_model)
175
+
176
+ self._table_view.verticalHeader().setVisible(False)
177
+
178
+ header = self._table_view.horizontalHeader()
179
+ header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
180
+ header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
181
+ header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeMode.Stretch)
182
+ header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeMode.Stretch)
183
+ header.setSectionResizeMode(4, QtWidgets.QHeaderView.ResizeMode.Stretch)
184
+
185
+ self._add_anchor_button.clicked.connect(
186
+ self._add_anchor_button_clicked)
187
+ self._remove_anchors_button.clicked.connect(
188
+ self._data_model.remove_selected_anchors)
189
+ self._get_from_anchors_button.clicked.connect(
190
+ self._get_from_anchors_button_clicked)
191
+ self._write_to_anchors_button.clicked.connect(
192
+ self._write_to_anchors_button_clicked)
193
+ self._close_button.clicked.connect(self.close)
194
+ self._load_button.clicked.connect(
195
+ self._load_button_clicked)
196
+ self._save_button.clicked.connect(
197
+ self._save_button_clicked)
198
+
199
+ def _add_anchor_button_clicked(self):
200
+ anchor_id, ok = QInputDialog.getInt(
201
+ self, "New anchor", "Enter id", min=0, max=255)
202
+ if ok:
203
+ self._data_model.add_anchor(anchor_id)
204
+
205
+ def _get_from_anchors_button_clicked(self):
206
+ self._data_model.replace_anchors_from_latest_known_positions()
207
+
208
+ def _write_to_anchors_button_clicked(self):
209
+ anchor_positions = self._data_model.get_anchor_postions()
210
+ self._lps_tab.write_positions_to_anchors(anchor_positions)
211
+
212
+ def anchor_postions_updated(self, anchor_positions):
213
+ self._data_model.anchor_postions_updated(anchor_positions)
214
+
215
+ def _load_button_clicked(self):
216
+ names = QFileDialog.getOpenFileName(self, 'Open file', self._helper.current_folder, FILE_REGEX_YAML)
217
+
218
+ if names[0] == '':
219
+ return
220
+
221
+ self._helper.current_folder = os.path.dirname(names[0])
222
+
223
+ f = open(names[0], 'r')
224
+ with f:
225
+ data = yaml.safe_load(f)
226
+
227
+ anchor_positions = {}
228
+ for id, pos in data.items():
229
+ anchor_positions[id] = (pos['x'], pos['y'], pos['z'])
230
+ self._data_model.replace_anchor_positions(anchor_positions)
231
+
232
+ def _save_button_clicked(self):
233
+ anchor_positions = self._data_model.get_anchor_postions()
234
+ data = {}
235
+ for id, pos in anchor_positions.items():
236
+ data[id] = {'x': pos[0], 'y': pos[1], 'z': pos[2]}
237
+
238
+ names = QFileDialog.getSaveFileName(self, 'Save file', self._helper.current_folder, FILE_REGEX_YAML)
239
+
240
+ if names[0] == '':
241
+ return
242
+
243
+ self._helper.current_folder = os.path.dirname(names[0])
244
+
245
+ if not names[0].endswith(".yaml") and names[0].find(".") < 0:
246
+ filename = names[0] + ".yaml"
247
+ else:
248
+ filename = names[0]
249
+
250
+ f = open(filename, 'w')
251
+ with f:
252
+ yaml.dump(data, f)